clean code
Ch.3 Functions
Small!
- Small is better
- Less than 20 lines are better
- Transparently obvious
- Each Function has their own story
Blocks and Indenting
- One line is enough
- Prevent nested structures
(if...else, while)
let Erica, Jeff, Alex;
OnceJeffKillErica();
function OnceJeffKillErica() {
if (Jeff kill Erica) {
Alex is crying
Alex kill Jeff
if (Jeff is dead){
Alex get in jail
if...
} else {
...
}
}
}
let Erica, Jeff, Alex;
OnceJeffKillErica();
function OnceJeffKillErica() {
if (Jeff kill Erica) {
JeffGetRevenge()
}
}
function JeffGetRevenge () {
Alex is crying
Alex kill Jeff
if (Jeff is dead){
AlexGetConsequence()
} else {
...
}
}
funciton AlexGetConsequence () {
Alex get in jail
if ()...
}
Do One Thing
- Single Responsibility, Single Functionality
-
Function name as responsibility
-
Extract another function only with restate its implementation.
function emailClients(clients) {
clients.forEach((client) => {
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
function emailActiveClients(clients) {
clients
.filter(isActiveClient)
.forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
Sections within Functions
- Can’t be divided anymore
One Level of Abstraction per Function
- Abstraction - Hiding stuff away
(https://www.youtube.com/watch?v=L1-zCdrx8Lk) - One level - (Frontend Engineer: View)
- Multiple level - (Fullstack Engineer: View & Data)
- Beyond Multiple level - (FullAplication Engineer: All)
- Stay abstraction in same level
function parseBetterJSAlternative(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
// ...
});
});
const ast = [];
tokens.forEach((token) => {
// lex...
});
ast.forEach((node) => {
// parse...
});
}
function parseBetterJSAlternative(code) {
const tokens = tokenize(code);
const ast = lexer(tokens);
ast.forEach((node) => {
// parse...
});
}
function tokenize(code) {
const REGEXES = [
// ...
];
const statements = code.split(' ');
const tokens = [];
REGEXES.forEach((REGEX) => {
statements.forEach((statement) => {
tokens.push( /* ... */ );
});
});
return tokens;
}
function lexer(tokens) {
const ast = [];
tokens.forEach((token) => {
ast.push( /* ... */ );
});
return ast;
}
Reading Code from Top to Bottom: The Stepdown Rule
- Top-down narrative
- Keep in same level of abstraction
Switch Statements
- Against do one thing rule
- Solution:
Abstraction & Polymorphism
class Airplane {
// ...
getCruisingAltitude() {
switch (this.type) {
case '777':
return this.getMaxAltitude() - this.getPassengerCount();
case 'Air Force One':
return this.getMaxAltitude();
case 'Cessna':
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
}
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
getCruisingAltitude() {
return this.getMaxAltitude() - this.getFuelExpenditure();
}
}
Use Descriptive Names
- Don’t be afraid to make a name long
-
Use a naming convention
example:
addToDate -> addMonthToDate
Function Arguments
- Ideal number of arguments
(nailadic - zero > monadic - one > dyadic - two > triadic - three) - Use Object as parameter instead
- Less argument easier test
- Output arguments are harder to understand than input arguments
tbd // call(a) → a output argument
// fun(a) → input argument
function createMenu(title, body, buttonText, cancellable) {
// ...
}
function createMenu({ title, body, buttonText, cancellable }) {
// ...
}
createMenu({
title: 'Foo',
body: 'Bar',
buttonText: 'Baz',
cancellable: true
});
Common Monadic Forms
- Common forms.
- Asking a question about argument
- Operating, transforming it into something else and returning it
- Event Handler
Flag Arguments
- Flags usually means more than one thing
- Separate it out to functions
function createFile(name, temp) {
if (temp) {
fs.create(`./temp/${name}`);
} else {
fs.create(name);
}
}
function createFile(name) {
fs.create(name);
}
function createTempFile(name) {
createFile(`./temp/${name}`);
}
Dyadic Functions
- Harder to understand than monadic
-
Easily to confuse with the orders
caculateData(expected, actual) -
Can't avoid in some case
Point p = new Point(0,0);
Triads
- Worse to understand than dyadic function
- Even more confuse with the orders
add
caculateData(message, expected, actual)
Argument Objects
- Reducing the number of arguments by wrapped them together to object.
Circle makeCircle(x, y, radius);
Circle makeCircle({x, y, radius});
Argument Lists
- Variable arguments sometime can be consider to single argument of type List.
String.format(”%s worked %.2f hours.”, name, hours);
Verbs and Keywords
- Function name
- Verb/Noun pair
- GetKnobData(pid) - Use Keyword
- GetGlobalBPM
- Verb/Noun pair
Have No Side Effects
- Side effect are Timebomb
(ex: writing to a file, modifying some global variable, listener...) - Side effect is unpreventable but we can centralize it.
- In Javascript, reference type mutation is also an side effect.
- resolve: Immutable.js
(https://facebook.github.io/immutable-js/)
- resolve: Immutable.js
const addItemToCart = (cart, item) => {
cart.push({ item, date: Date.now() });
};
const addItemToCart = (cart, item) => {
return [...cart, { item, date: Date.now() }];
};
Output Arguments
- //Output arguments is difficult to tell compare to Input arguments
- output -> add(a)
- input -> function add(augend)
- //Change the state from it own object rather than from output arguments invoke
- add(data) -> data.add()
Command Query Separation
- Functions should either do something or answer something, but not both
public boolean set(String attribute, String value);
if (set(”username”, ”unclebob”))…
if (get(”username”, ”unclebob”))…
Prefer Exceptions to Returning Error Codes
- Catch the exception than handle the return error
if (deletePage(page) === true) {...}
try {
deletePage(page) === true)
...
} cache(e) {...}
Extract Try/Catch Blocks
- Try/Catch error handle logic could be confused the different process so it should be handle in their own
try {
deletePageAndAllReferences(page);
}
catch (Exception e) {
logError(e);
}
Error Handling Is One Thing
- Error handle is one thing
- A function that handles errors should do nothing else
The Error.java Dependency Magnet
- Centralize the error code to one place such as class
- Reuse the error code instead of adding new
Don’t Repeat Yourself
- Duplication is the root evil of software
Structured Programming
- Keep your functions small is a best structure
How Do You Write Functions Like This?
-
Writing software is like any other kind of writing, no need to be perfect at first
Conclusion
Short, well named, and nicely organized
tell the story of the system rather than programs to be writte
Most importantly:
Clean Code
By Chien Chieh Wang
Clean Code
- 92