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

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.
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