Writing clean code

42
Writing clean code Mobiquity - Ángel García (Mobile developer)

Transcript of Writing clean code

Writing clean codeMobiquity - Ángel García (Mobile developer)

What is all this about?

Table of contents• What is clean code? • Why is readability important? • Code style

• Code formatting • Naming conventions • Code comments

• Code structure • Control sentences • Functions and methods • Classes

• Architecture level • Modules • Software patterns • Programming paradigms

• Tools

Before we startA bit of context

• No universal definition !

• Difference between clean code, working code and good code • Clean code: readable, maintainable, extensible • Working code: does what it is supposed to do • Good code: combination of clean and working code

What is clean code?

Working code

Clean code

Good code

Why is readability important?• The process of changing code:

1. Find component 2. Find affected lines 3. Write 4. Test !

• Reading —> 80/20 !

• Readability: • Save your time • Improve maintenance • Increase extensibility

Code formattingReAD Ing iS nO t Al Wa iS aS E as Y As IT cO uLd bE

Code formatting{"id":10,"childs":[{"id":1,"title":"A"},{"id":2,"title":"B"}],"active": true,"title": "Hello World”}

{ "id": 10, "childs": [ { "id": 1, "title": "A" }, { "id": 2, "title": "B" } ], "active": true, "title": "Hello World" }

Code formatting• Brain is good on recognising patterns

• Vertical space • Horizontal space • Expressions • Casing !

• Text search along project !

• Follow language styles !

• Be consistent

Naming conventionsWhat the hell does this mean?

Naming conventions• The importance of naming. What does this code do? !func doMyStuff(param1: Bool, param2: Bool, param3:Int) -> Int { if param3 > 0 { var a = 0 if param3 % 2 == 0 && param2 { a = param3 } else if param3 % 2 == 1 && param1 { a = param3 } return a + doMyStuff(param1, param2, param3 - 1) } else { return 0 } } !doMyStuff(true, true, 4)

Naming conventions• The importance of naming. What does this code do? !func summation(fromNumber:Int, includeEven: Bool = true, includeOdd: Bool = true) -> Int { if fromNumber > 0 { var currentSum = 0 if fromNumber % 2 == 0 && includeOdd { currentSum = fromNumber } else if fromNumber % 2 == 1 && includeEven { currentSum = fromNumber } return currentSum + summation(fromNumber - 1, includeEven: includeEven, includeOdd: includeOdd) } else { return 0 } } !summation(4)

Naming conventions• Names should be explicit and clear temp.active = YES; product.active = YES; !

!• Provide context inactiveProducts.count; !!

• Avoid shortcuts !

• Avoid Business/Technical names when possible !

• Follow language conventions (ivars, setters/getters, casing,…) !

• Be consistent (remove/delete, create/insert,…)

Code comments“Now hiring now. Right now we are hiring now”

Code comments• Issues

• Can be outdated (misleading) • Require language switch

!• Types

• Redundant //Start reachability notifications [[Reachability reachabilityForInternetConnection] startNotifier]; !!

• Separate blocks with different responsibilities - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)index { //Create cell UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier]; … //Configure cell … return cell; } !

• Commented-out code

Code comments• Code is not self-explanatory • Code has side effects

//Careful! setting delegate reloads table content self.tableView.delegate = self; !

• Domain specific (complex math, business rules,…) • Public APIs in libs

/** Loads Purchase history and returns MOReceipts @param block Block executed when the history is loaded. Contains parsed MOReceipts @param errorBlock Block executed when an error occurs */ - (void)updateReceiptsOnSuccess:(MOResponseArrayBlock)block error:(MOErrorBlock)errorBlock;

!

• In general, avoid comments

Control sentencesWhere the linear flow ends

Control sentences• Format • Conditionals:

• Simple expressions —> variables if (price - price * discountPercentage + discountAmount < minimumPrice) { println("Difficult clause") } !let amountToPay = price - price * discountPercentage + discountAmount if (amountToPay < minimumPrice) {} !

• Do not use more than 3 or 4 expressions • Prefer positive clauses

var notForbidden = false if !notForbidden {}

! !• Do not use assignments or side effects

if ((x = y * 2) != 0) {} if (self = [super init]) {} if (condition && [self destroyObject]) {}

Control sentences• Avoid Spaghetti code (nested depth < 3)

if (condition1) { if (condition2) { for(i = 0; i < 10; i++) { if (condition4) { if (condition5) { } return; } } } } ! !

• Collapse when possible if (condition1) { if (condition2) { } } !if (condition1 && condition2) { } !

• Do not use goto, break, continue

Control sentences• “if” statements

• Avoid empty cases • Avoid obvious “else if” clauses

if flag { } else if !flag { println("Difficult to read") }

• “for” loops • Do not abuse

for (;condition;) { /*...*/ } • Do not modify the conditional variable inside

for (i = 0; i < 10; i++) { i *= 2; } !

• Ternary operators • Only use for assignments

condition? [self method1] : [self method2]; name = user.name? : @"Anonymous"; !

• Must be extremely simple

Functions and methodsYour code in small steps

Functions and methods• Clear naming

summation(4) !

• 1 Level of abstraction let dates = downloadAndParse(“http://myrurl.com”)

!• Single Responsibility Principle (SRP)

“Every context should have a single responsibility, and that responsibility should be entirely encapsulated by the context” - Wikipedia !

• No side effects !

• Explicit error handling - (BOOL)writeToURL:(NSURL *)aURL options:(NSDataWritingOptions)mask error:(NSError **)errorPtr;

Functions and methods• Parameters

• Prefer over instance variables (or globals/statics) • Avoid output • Reduce amount (<4) [NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0.0] !

• 1 point of exit !

• Remove empty methods or only calling super !

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { } return self; }

ClassesYour building blocks

Classes• SRP. Check yourself:

• Name should describe responsibility • Describe functionality without “and”, “or”, “but”, “if” • Do you have flags? !

• Open-Close principle “Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification” - Wikipedia !• Hide all implementation details • Simplify public headers/methods/properties !

• Explicit dependencies !

• Sort you methods and properties (public/private)

Classes• Avoid statics

!

• Use polymorphism if (a.isKindOfClass(MyClass)) { !

!• Take advantage of categories

MyProjectUtils.convertToUrlEncoding("hello") StringUtils.convertToUrlEncoding(“hello") “hello".convertToUrlEncoding() !"hello".drawInRect(rect:rect, withAttributes: attributes) !

• Sort your toolbox!

ModulesIntegrating all the pieces

Modules• SoC

“Design principle for separating a computer program into distinct sections, such that each section addresses a separate concern. A concern is a set of information that affects the code of a computer program”. Wikipedia !

• Explicit • API • Callbacks (protocols, blocks,…) • Threads !

• Split by functionality vs type !

• Hide internals !

• Structure your data - (NSDictionary *)findNearestPersonToLocation:(NSArray *)coordinates; - (Person *)findNearestPersonToLocation:(CLLocationCoordinate2D)coordinate;

Modules• Dependencies

• Law of Demeter “LoD can be succinctly summarized in one of the following ways: • Each unit should have only limited knowledge about other units: only units "closely" related to the current unit. • Each unit should only talk to its friends; don't talk to strangers. • Only talk to your immediate friends.”. Wikipedia

!aVar.getB().getC() !

!• Avoid cycles

!

• Prefer Interfaces vs concrete classes !

• Isolate lifecycle !

• Make proper use of Software patterns

A B C

Software patternsEverybody can ride a bike

Software patterns• Robust, flexible and well known solutions

!

• Reduce cognitive load “Among psychologists it is widely acknowledged that, compared to "cold" learning, people learn more effectively when they can build on what they already understand”. Wikipedia. !

• Some of most interesting: • MVC / MVVM • Inheritance / Composition • Inversion Of Control / Dependency Injection • Delegate/Observer • Factory • Chain Of Responsibility • ….

MVC• Model

• Contains information in Plain objects • Connects to endpoints, DB,.…

• View • Draws the screen • Receives/passes events to/from controllers

• Controller • Connects the View and the Model layers !

• Most common pitfalls in iOS • Massive controllers (DataSources, components,…) • Controllers doing Model/View work • View & Controller coupling

!

• Alternative: MVVM (Model-View-ViewModel)

Inheritance vs Composition

• Inheritance is good for “is-a” relations • Use composition as default

Inheritance Composition

Difficult to keep SRP Easy to keep SRP

Details from base class to subclasses Close components

Massive base classes Small components

Behaviour on compilation time Behaviour on runtime

Inversion of Control / Dependency Injection• Increase reusability of modules • Lifecycle controlled on system level • IoC

• Invert relation code-3rd party • Generic API definitions • Dependencies are provided

• DI • Dependencies are injected (constructors, setters)

!class TextEditor { var checker = SpellCheckerES() //TextEditor coupled with ES checker } !class TextEditor { var checker: SpellChecker init(checker: SpellChecker) { self.checker = checker //Implementation of checker decided by DI controller } }

Programming paradigmsLearning from others

Aspect Oriented Programming• Ideal for cross-cutting concerns

!

• Hooks code before/after methods !

• Aspects (https://github.com/steipete/Aspects) [UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) { NSLog(@"View Controller will appear"); } error:nil];

Reactive programming• Ideal for event-driven datasources

!

• Automatic propagation of changes !

• Reduces/hides state • Time independent code

!

• Reactive Cocoa (https://github.com/ReactiveCocoa/ReactiveCocoa) RAC(self.logInButton, enabled) = [RACSignal

combineLatest:@[ self.usernameTextField.rac_textSignal, self.passwordTextField.rac_textSignal, RACObserve(LoginManager.sharedManager, loggingIn) ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn) { return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue); }];

Functional programming• Mimic mathematical functions

!

• Avoids state !

• Outputs only dependent on inputs • No side effects • Parallelizable • Predictable (race conditions) !

• Chainable operations !

let evenSum = Array(1...10) .filter { (number) in number % 2 == 0 } .reduce(0) { (total, number) in total + number }

ToolsComputers can also help you

Tools• Uncrustify (http://uncrustify.sourceforge.net/)

!

• OCLint (http://oclint.org/) !

• Sonar for Objective-C (https://github.com/octo-technology) !

• Objc-dependency-visualizer (https://github.com/PaulTaykalo/objc-dependency-visualizer)

References• Clean code: A handbook of Agile Software Craftsmanship

!

!

!

!

!

!

!

!

!

!

• Objc.io (http://www.objc.io/)

Thank you!Q&A