Clean Code



Clean Code

0 0


clean-code

Presentation slides for my talk Clean Code using Reveal.js

On Github cvuorinen / clean-code

Clean Code

W3 School 7.4.2014

by Carl Vuorinen / @cvuorinen

What is Clean Code?

Short?

Simple?

Readable?

Self documenting?

Testable?

SOLID?

DRY?

What makes code simple and readable?

Naming things

There are only two hard things in Computer Science: cache invalidation and naming things. Phil Karlton - Convey meaning, intent - Consistency - Don't abbreviate - If naming is hard, maybe method/class is doing too much - Domain name vs. Computer Sience name
function isMachine($name)
                    
public function isMachine($name)
{
    return str_is($name, gethostname());
}
                    
github.com/laravel/framework/blob/master/src/Illuminate/Foundation/EnvironmentDetector.php#L98
public function hostnameMatches($name)
                    
public function isCurrentHost($hostname)
                    
"is" beginning implies boolean

What is the meaning of this?

State your intentions.

Say what you mean. Mean what you say.

Minimize guess work and possibility for misinterpretation.

- Clear API - Self documenting

Comments

Why, not What

$all_permutations = array(array());
foreach ($parameters as $parameter => $values) {
    $new_permutations = array();
    // Iterate over all values of the parameter.
    foreach ($values as $value) {
        // Iterate over all existing permutations.
        foreach ($all_permutations as $permutation) {
            // Add the new parameter value to existing permutations.
            $new_permutations[] = $permutation + array($parameter => $value);
        }
    }
    // Replace the old permutations with the new permutations.
    $all_permutations = $new_permutations;
}
                    
github.com/drupal/drupal/blob/7.0/modules/simpletest/drupal_web_test_case.php#L615
$all_permutations = array(array());
foreach ($parameters as $parameter => $values) {
    // To create all possible permutations,
    // combine current values with all existing permutations
    $new_permutations = array();
    foreach ($values as $value) {
        foreach ($all_permutations as $permutation) {
            $new_permutations[] = $permutation + array($parameter => $value);
        }
    }
    $all_permutations = $new_permutations;
}
                    

Avoiding complexity

  • Long methods
  • Nested control structures
  • Classes with many responsibilities
  • Too many dependencies
  • Too much abstraction
  • Overengineering
- Every dev is a designer
// Use the user's block visibility setting, if necessary.
if ($block->custom != BLOCK_CUSTOM_FIXED) {
    if ($user->uid && isset($user->data['block'][$block->module][$block->delta])) {
        $enabled = $user->data['block'][$block->module][$block->delta];
    }
    else {
        $enabled = ($block->custom == BLOCK_CUSTOM_ENABLED);
    }
}
else {
    $enabled = TRUE;
}
					
github.com/drupal/drupal/blob/7.x/modules/block/block.module#L783
$enabled = TRUE;

if ($block->custom != BLOCK_CUSTOM_FIXED) {
    $enabled = ($block->custom == BLOCK_CUSTOM_ENABLED);

    if ($user->uid && isset($user->data['block'][$block->module][$block->delta])) {
        $enabled = $user->data['block'][$block->module][$block->delta];
    }
}
					
// Limit publicly queried post_types to those that are publicly_queryable
if ( isset( $this->query_vars['post_type']) ) {
    $queryable_post_types = get_post_types( array('publicly_queryable' => true) );
    if ( ! is_array( $this->query_vars['post_type'] ) ) {
        if ( ! in_array( $this->query_vars['post_type'], $queryable_post_types ) )
            unset( $this->query_vars['post_type'] );
    } else {
        $this->query_vars['post_type'] = array_intersect( 
            $this->query_vars['post_type'], 
            $queryable_post_types
    	);
    }
}
					
github.com/WordPress/WordPress/blob/master/wp-includes/class-wp.php#L298
if (isset($this->query_vars['post_type'])) {
    $this->query_vars['post_type'] = $this->limitPubliclyQueriedPostTypes(
        $this->query_vars['post_type'],
        get_post_types( array('publicly_queryable' => true) )
    );
}
                    
private function limitPublicPostTypes($post_types, array $queryable_post_types)
{
    if (is_array($post_types)) {
        return array_intersect($post_types, $queryable_post_types);
    }

    if (in_array($post_types, $queryable_post_types)) {
        return $post_types;
    }

    return;
}
					
/**
 * Filter array or string to only those values that are allowed
 * 
 * @param array|string $values         String value or an array of values
 * @param array        $allowed_values Values that are allowed
 * 
 * @return array|string Value(s) that are allowed
 */
private function filterArrayOrString($values, array $allowed_values)
{
    if (is_array($values)) {
        return array_intersect($values, $allowed_values);
    }

    if (in_array($values, $allowed_values)) {
        return $values;
    }

    return;
}
					

Object Calisthenics

  • One level of indentation per method
  • Don't Use The ELSE Keyword
  • Wrap all Primitives And Strings
  • First class collections
  • One dot (arrow) per line
  • Don't abbreviate
  • Keep all entities small
  • No classes with more than two instance variables
  • No getters/setters/properties

”Magic”

Unseen control flow makes code harder to follow

  • Magic methods
  • Events
  • AOP
- Use moderately

preg_match('/a{5,}rgh/')

preg_match('/^[a-z][a-z\d_]+$/i', $string);
                    
$usernamePattern = "/"
    . "^[a-z]"     // Must begin with a alphabetic character
    . "[a-z\d_]+$" // Then 1-n alphanumeric characters and/or underscores
    . "/i";        // case-insensitive

preg_match($usernamePattern, $username);
                    
- Store regex in vars/constants to give them names. => Reader isn't distracted by the regex.
} elseif (preg_match('/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*
    (?:\\\\.[^\'\\\\]*)*)\'/As', $expression, $match, null, $cursor)
) {
    // strings
    $tokens[] = new Token(
        Token::STRING_TYPE,
        stripcslashes(substr($match[0], 1, -1)), 
        $cursor + 1
    );
    $cursor += strlen($match[0]);
} elseif (preg_match('/not in(?=[\s(])|\!\=\=|not(?=[\s(])|and(?=[\s(])|\=\=\=|\>\=|
    or(?=[\s(])|\<\=|\*\*|\.\.|in(?=[\s(])|&&|\|\||matches|\=\=|\!\=|\*|~|%|\/|\>|\|
    |\!|\^|&|\+|\<|\-/A', $expression, $match, null, $cursor)
) {
    // operators
    $tokens[] = new Token(Token::OPERATOR_TYPE, $match[0], $cursor + 1);
    $cursor += strlen($match[0]);
}
                    
github.com/symfony/symfony/blob/v2.4.0/src/Symfony/Component/ExpressionLanguage/Lexer.php#L72

Don't try to be Clever™

Play Golf on your own time

- Professionals write code that others can understand

Simplicity is relative

- Junior vs. Senior - Abstractions and small pieces harder to follow for junior
private function getSessionId(Response $response)
{
    /** @var Header $cookies */
    $cookies = $response->getHeader('Set-Cookie');

    return ArrayCollection::create($cookies->parseParams())
        ->map(
            function (array $headerParameters) {
                return isset($headerParameters['SESSION_ID']) ?
                    $headerParameters['SESSION_ID'] :
                    false;
            }
        )
        ->find(
            function ($parameter) {
                return $parameter;
            }
        );
}
                    
- Should be commented because not obvious to every dev.
private function getSessionId(Response $response)
{
    /** @var Header $cookies */
    $cookies = $response->getHeader('Set-Cookie');

    foreach ($cookies->parseParams() as $headerParameters) {
        if (isset($headerParameters['SESSION_ID'])) {
            return $headerParameters['SESSION_ID'];
        }
    }

    return false;
}
                    

Functional Programming

Immutable variables + functions without state = no side effects

- Easier to understand and debug and test.

Testing

Tests are a form of documentation

They need to be easily readable

Why?

What's wrong with code that "just works"?

Programming is the art of telling another human what one wants the computer to do. Donald Knuth

Code is written once, but read many times.

Adding complexity slows you down tomorrow.

- Easier to test, fix bugs, maintain - Future is the most important thing (most of the time) - Someone (most of the time you) will have to maintain the code
If you want your code to be easy to write, make it easy to read. Robert C. Martin - Have to read surrounding code when writing new code

Good Enough?

Maintenance vs. Implementation

How?

Ask yourself

Does it make sense?

How well can others understand it?

How could this be easier to understand and use?

- Read each line/method/class in isolation

Metrics

Static code analysis

- Mess detector, pdepend, Jenkins, SonarQube
- Development by the numbers (ircmaxell)

Coding Standard

Code Review

- Peer feedback - Lead developer

Future is the most important thing

Don't make assumptions about the future

- Things will change - Make code that is easy to change

Constant Refactoring

Broken window theory

Remove complexity instead of adding it

Hide complexity you cannot remove

- When adding new features, fixing bugs - Design patterns - YAGNI, Remove unused code - Facade (hide complexity)

Clean code doesn't usually happen on first try.

You have to iterate and refractor.

Clean code always looks like it was written by someone who cares. There is nothing obvious you can do to make it better. Michael Feathers

Further reading

   

Further reading

"Code is like humor. When you *have* to explain it, it’s bad" - @housecor

— About Programming (@abt_programming) March 24, 2014

“The cheapest time to refactor code is right now.” Great advices by @bugroll http://t.co/GGEQ8Bc3e1 #refactoring #cleancode

— Luca Guidi (@jodosha) March 20, 2014

Beautiful code, like beautiful prose, is the result of many small decisions. The right method length here, the right object name there.

— DHH (@dhh) March 21, 2014

Your coding standard is in the code. A Word document about your standard says the standard isn't followed #cleancode #agile

— lpcarignan (@lpcarignan) October 25, 2011