☕ A summary of Clean Code
Naming Conventions in Clean code. Plus, the different types of technical debt and why they're not equal!
Hey Everyone,
Hope you’re all having a fantastic day!
Today’s email will have…
a summary of Clean Code. We’ll be giving a chapter by chapter summary throughout the next week. Today will be on Clean Code’s recommendations for naming.
Technical debt and the different types of tech debt (they’re not all equal!).
Switching from a NoSQL database to a SQL db.
We also have a solution to our last (hard) interview question and a new interview question from Microsoft!
Are you signed up for Quastor Daily?
Get an email with
Detailed summaries of software engineering books
FAANG Interview Questions (with detailed solutions)
Links and summaries of the best technical blog posts
It’s completely free!
Clean Code - Naming
When you’re writing code, you’re constantly coming up with names. Naming classes, functions, variables, objects, etc.
An (often underlooked) aspect of writing clean code is to come up with good names. Having a great naming scheme makes your code much easier to read.
There are only two hard things in Computer Science: cache invalidation and naming things. ~ Phil Karlton
Here are some rules from Clean Code’s chapter on Naming
Use Intention-Revealing Names
The name of a variable, function or class should tell you why it exists, what it does and how it’s used.
If a name requires a comment, then the name does not properly reveal it’s intent!
This is bad
int diff; // time difference between two days in hours
This is good
int timeDiffInHours;
Avoid Disinformation
Don’t let your names leave false clues!
For example, the i
and j
variables are frequently used as counters inside a loop.
This convention comes from Sigma notation in mathematics.
Therefore, don’t use i
or j
inside a loop for something that’s not a loop counter!
Make Meaningful Distinctions
Every programmer faces this situation. You’re trying to name a variable but you already have another variable with the same name that’s in the same scope.
What do you do?
Many programmers do the classic number-series naming (a1
, a2
, a3
, a4
, etc.)
Don’t do this! Avoid changing the new name in some arbitrary way just to satisfy the compiler.
Instead, put effort into giving the new name a meaningful distinction.
Use Pronounceable Names
If you can’t pronounce the name, it becomes harder to discuss the variable, function, object, etc. in a conversation.
Therefore, try and make the names pronounceable.
Use Searchable Names
Every now and then, you’ll want to search for a variable in your code.
You can easily grep for a name like “get_customer_activation_date” but if you have a name like “se” then it could get difficult to search for it in a large codebase.
Clean code recommends that you only use single-letter names as local variables inside short methods.
The length of a name should correspond to the size of it’s scope
Rules for Class & Method Names
Classes and Objects should have noun or noun phrase names.
Examples - Customer
, WikiPage
, AddressParser
Methods should have verbs or verb phrase names
Examples - postPayment
, deletePage
Pick One Word Per Concept
Pick one word for an abstract concept and stick with it!
It’s confusing to have fetch_
, retrieve_
, get_
, etc. all as prefixes for equivalent methods of different classes in a single codebase.
Pick one!
Otherwise, it becomes difficult to keep track of which method prefix goes with which class.
Are you signed up for Quastor Daily?
Get an email with
Detailed summaries of software engineering books
FAANG Interview Questions (with detailed solutions)
Links and summaries of the best technical blog posts
It’s completely free!
Tech Snippets
In software engineering, you’ll often have to choose between shipping a feature quickly (and later refactoring your code) vs. building the feature slowly (and implementing it in a more robust manner).
In many cases, it makes sense to take on technical debt, because you don’t know if the feature is something the customer wants.
It would be pointless to spend a huge amount of engineering time on a feature the customer didn’t even want!
Technical debt can be categorized into 3 buckets
Code
This is the most common type of tech debt. This refers to suboptimal code that has been committed.
An example is writing one big function that does everything. Later, you’ll have to refactor that function down into smaller components that are well designed.
Code technical debt is also the easiest to fix. If you have good tests set up, then refactoring the code base isn’t too bad.
Data
This refers to tech debt around the data model you’ve chosen.
Teams can sometimes pick a very flexible data model for early iteration and flexibility
Example - Let’s just put everything in one JSON object and then do all the filtering client side!
Your data models is one of the hardest things to get right, and also one of the hardest things to change.
Architecture
This refers to design considerations around the system architecture, runtime choices, interfaces, etc.
Changing your database from SQL to NoSQL (or vice-versa) can be very painful!
If you want to read a fascinating story about going from NoSQL to SQL, read the next tech snippet!
Switching from MongoDB to NoSQL for a social networking application
If you’re building a social network (people are objects and the people are connected to other people, etc.), what kind of database should you use?
For many years, the wisdom was that social data is not relational, and therefore storing it in a relational database was wrong.
Some argue that document databases are a better fit, so the engineering team in the story decided to first go with MongoDB.
However, after eight months they realized their data would be better represented with a relational database and had to switch to MySQL in a month-long process.
Read the blog post for an interesting discussion on document-driven databases and why your use case might fit better with a relational model!
Are you signed up for Quastor Daily?
Get an email with
Detailed summaries of software engineering books
FAANG Interview Questions (with detailed solutions)
Links and summaries of the best technical blog posts
It’s completely free!
Interview Question
Given a string s
, check if it can be constructed by taking a substring of it and appending multiple copies of the substring together.
Here’s the question in LeetCode.
Previous Solution
As a refresher, here’s the last question
Given an integer array nums
that may contain duplicates, return all possible subsets.
The solution set must not contain duplicate subsets. Return the solution in any order.
Here’s the question in LeetCode.
Solution
This question is asking us to return the power set given a set of integers (nums
).
The power set is defined as the set of all subsets (including the empty set and the set itself).
In the worst case (where nums
is not empty and consists of all unique integers), there will be 2^N subsets in our power set (where N is the number of integers in nums
).
Therefore, our worst case time complexity will be at least O(2^N).
Finding the power set will require a backtracking approach.
With backtracking, you incrementally build up your solution until you reach some block (no more solutions are possible). Then, you recurse back up the stack and incrementally build solutions in a different direction.
Let’s apply backtracking to the given question. Let’s say nums
is [1,2,3].
We’ll use the array powerset
to be an array of arrays to keep track of all the subsets. After we finish backtracking, our function can just return powerset
.
We’ll use curSol
to be our current solution. We’ll start with curSol
being equal to an empty array.
The empty set is one valid subset of nums
, so we’ll add curSol
to powerset
.
Now, we’ll add 1 to curSol
, making it [1].
This is a valid subset, so we add it to powerset
.
We’ll continue down this path and add 2 to curSol
, making it [1,2].
Another valid subset, so we add it to powerset
.
We continue down and add 3 to curSol
, making it [1,2,3].
Again, a valid subset, so we add it to powerset
.
Now, we’ve reached a block. There are no more integers in nums
to add to curSol
.
Therefore, we’ll back out of the current solution by popping 3 off curSol
, making curSol
equal to [1,2].
However, since we’ve already gone down the 3 path, there still aren’t any numbers in nums
that we can add to curSol
.
Therefore, we’ll back out of the current solution by popping 2 off, making curSol
equal to [1].
Now, we have the 3 path to explore. Therefore, we’ll add 3 to curSol
, making it [1,3].
This is a new valid subset, so we’ll add it to our powerset
.
At this point, we’ve reached another block. There are no new integers in nums
that we can add to curSol
.
Therefore, we’ll pop off 3 and go back to where curSol
is [1].
We’re still at a block, since there are no new integers in nums
.
So, we’ll pop off 1, and go back to the empty set.
Now, we’ll add 2 to curSol
, making curSol
equal to [2]. Again, a valid subset, so we add it to powerset
. Then, we continue down the [2,] solution.
Hopefully, you’re getting the gist of how the backtracking approach works now.
Here’s the Python 3 code.
What’s the time/space complexity of our code?
Reply back with your best estimate and we’ll tell you if you’re right or wrong!
Are you signed up for Quastor Daily?
Get an email with
Detailed summaries of software engineering books
FAANG Interview Questions (with detailed solutions)
Links and summaries of the best technical blog posts
It’s completely free!