Uncle Bob’s Clean Code: Simple and Effective Tips
Clean code is essential for maintainable and readable software. Inspired by Uncle Bob’s principles from his video on clean code, here are some key takeaways and practical tips to help you write cleaner, more effective code using Swift.
Function Names Should Be Verbs, Not Nouns
Function names should clearly describe the action they perform. This makes your code more readable and easier to understand.
Example:
// Bad
func dataProcessor(data: Data) {
// process data
}
// Good
func processData(data: Data) {
// process data
}
Characteristics of Bad Functions
Large Number of Lines
Functions should be concise. A function with too many lines is hard to understand and maintain.
Doing More Than One Thing
A function should do one thing and do it well. If a function is performing multiple tasks, it should be split into smaller functions.
Mixing High and Low-Level Implementation
Functions should operate at a single level of abstraction. Mixing high-level logic with low-level details makes the function harder to read.
Example:
// Bad
func processOrder(order: Order) {
// high-level logic
validateOrder(order)
// low-level details
for item in order.items {
if item.stock < 1 {
fatalError("Item out of stock")
}
}
// more high-level logic
saveOrder(order)
}
// Good
func processOrder(order: Order) {
validateOrder(order)
checkStock(order)
saveOrder(order)
}
func checkStock(order: Order) {
for item in order.items {
if item.stock < 1 {
fatalError("Item out of stock")
}
}
}
Unit Tests Give Confidence in Refactoring Legacy Code
Unit tests ensure that your code works as expected. They are particularly valuable when refactoring legacy code, as they provide a safety net that helps prevent the introduction of bugs.
Write Code for the Reader, Not Just for It to Work
When writing code, consider the reader. Write your code as if the next person to read it is a sociopath who knows where you live.
Example:
// Bad
func cnvrt(v: Double) -> Double {
return v * 0.393701
}
// Good
func convertCentimetersToInches(centimeters: Double) -> Double {
return centimeters * 0.393701
}
Use Explanatory Variables
Explanatory variables make your code more understandable by providing context for what values represent.
Example:
// Bad
func calculateSpeed(distance: Double, time: Double) -> Double {
return distance / time
}
// Good
func calculateSpeed(distanceInMeters: Double, timeInSeconds: Double) -> Double {
let speedInMetersPerSecond = distanceInMeters / timeInSeconds
return speedInMetersPerSecond
}
Have Small Functions
Small functions are easier to read, understand, and maintain.
When Is a Function Small Enough?
A function is small enough if you can still extract another function from it.
Example:
// Initial function
func processOrder(order: Order) {
validateOrder(order)
for item in order.items {
if item.stock < 1 {
fatalError("Item out of stock")
}
}
saveOrder(order)
}
// Refactored with smaller functions
func processOrder(order: Order) {
validateOrder(order)
checkStock(order)
saveOrder(order)
}
func checkStock(order: Order) {
for item in order.items {
if item.stock < 1 {
fatalError("Item out of stock")
}
}
}
Use Switch Statements Only if Cases Are Independently Deployable
Switch statements can be useful, but only if each case can be independently deployed. This ensures modular and maintainable code.
Example:
// Bad
func handleEvent(event: Event) {
switch event.type {
case .click:
handleClick(event)
case .hover:
handleHover(event)
default:
break
}
}
// Good
func handleEvent(event: Event) {
let eventHandlers: [EventType: (Event) -> Void] = [
.click: handleClick,
.hover: handleHover
]
if let handler = eventHandlers[event.type] {
handler(event)
}
}
Golden Rule in Naming Variables
Length of Variable Name Should Be Proportional to Scope
The length of a variable name should be proportional to its scope. Shorter names are fine for small scopes, while longer names are better for larger scopes.
Example:
// Small scope
for i in 0..<10 {
print(i)
}
// Large scope
func calculateAnnualRevenue(totalMonthlyRevenue: Double) -> Double {
let annualRevenue = totalMonthlyRevenue * 12
return annualRevenue
}
Golden Rule in Naming Functions
Length of Function Name Should Be Inversely Proportional to Scope
The length of a function name should be inversely proportional to its scope. General-purpose functions with broad applicability should have shorter names, while more specific functions with narrower scope should have longer, more descriptive names.
Example:
// Small scope, very specific function
func calculateTotalAnnualRevenueWithTax(monthlyRevenue: Double, taxRate: Double) -> Double {
let annualRevenue = monthlyRevenue * 12
let totalRevenueWithTax = annualRevenue + (annualRevenue * taxRate)
return totalRevenueWithTax
}
// Large scope, general-purpose function
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}