Tips to embody clean code

tkrtmy1031

Takeru Tomii

Posted on April 21, 2024

Tips to embody clean code

Use C# to consider each tip.

Principals

  1. High-cohesive, Loose coupling
  2. Stick to "Single responsiblity" to keep 1.
  3. Improve codes incrementally.

Basics

Naming

type words case example
Class noun, singular large camel case Client
Method verb small camel case addClient()
Method(boolean) boolean prefix, state small camel case isValidName()
Variable(primitive) noun, singular small camel case clientName
Variable(collection) noun, plural small camel case clientNames
Variable(constant) noun, singular large snake case MAX_CLIENT_NUM
Variable(boolean) boolean prefix, state small camel case isCanceledClient()
  • boolean prefix: is,has,can,should
  • Use unique, detailed and meaningful vocabulary
  • Avoid generic words
  • method -> verb for operation, class -> noun as purpose of method
  • Separate names in diffirent concerns with detail informations
    • e.g) Product -> OrderedItem, InventryItem
    • Detect it when you call the noun with different adjectives to express.
  • Read Terms of Service.
    • It indicates proper naming and separation of conserns.
  • Be Adherent to its business concerns or purposes.
    • Don't stick to technical sections.

Magic numbers

Move literals into constants.

BAD:

for(int i = 0; i < collection.length; i++) {
  if(client[i].age >= 60) {
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

GOOD:

const int DISCOUNT_AGE = 60;
for(int i = 0; i < collection.length; i++) {
  if(client[i].age >= DISCOUNT_AGE) {
    ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Not NULL

  • Don't initialise / pass / return a NULL value
  • Set initialised value object

Dead code

  • Remove all dead codes
  • DON'T comment it out

Logics

Early return / continue / break

Return earlier as much as possible to reduce nests

BAD:

if(isReserved) {
  if(shouldWithGardians) {
    if(client[i].price > client[j]) {
      ...
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

GOOD:

if(!isReserved) {
  return;
}
if(shouldWithGardians) {
  return;
}
if(client[i].price > client[j]) {
  return;
}

...
Enter fullscreen mode Exit fullscreen mode
  • Especially useful for writing guard clauses(validation)

Strategy pattern

Use interface to reduce redundant if-else or switch clauses

BAD:

class Shape {
  ...
  int calcArea() {
    readonly int area;
    // you'll add case when you create another Shape.
    switch(type)
    {
      case "Rectangle":
        area = width * height
      case "Triangle":
        area = (width * height) / 2;
      default:
        throw Exception();
    }
    return area;
  }
}

Enter fullscreen mode Exit fullscreen mode

GOOD:

interface Shape {
  ...
  int calcArea();
}

public class Rectangle:Shape {
  ...
  int calcArea() {
    return width * height;
  }
}
public class Triangle:Shape {
  int calcArea(){
    return (width * height) / 2;
  }
}

static void showArea(Shape shape) {
  Console.WriteLine(shape.calcArea());
}

static int main () {
  Shape rectangle = new Rectangle();
  showArea(rectangle);
  Shape triangle = new Triangle();
  showArea(triangle);
}
Enter fullscreen mode Exit fullscreen mode

Policy pattern

Super useful to componentise and assemble a set of conditions.

BAD:

// has dupulicate condition
bool isTriangle(Shape shape){
  if(hasClosedArea(shape)) {
    if(shape.angles.Count() == 3) {
      return true;
    }
  }
  return false;
}

bool isRectangle(){
  if(hasClosedArea(shape)) {
    if(shape.angles.Count() == 4) {
      return true;
    }
  }
  return false;
}
Enter fullscreen mode Exit fullscreen mode

GOOD:

// Create components of rules sharing common interface.
interface ShapeRule() {
  boolean ok(Shape shape);
}

class ClosedAreaRule : ShapeRule {
  boolean ok(Shape shape) { 
    return hasClosedArea(shape);
  }
}

class RectangleAnglesRule : ShapeRule {
  boolean ok(Shape shape) { 
    return shape.angles.Count() == 3;
  }
}

class TrianglesAnglesRule: ShapeRule {
  boolean ok(Shape shape) { 
    return shape.angles.Count() == 4;
  }
}


class ShapePolicy() {
  // Keep set of rules
  private readonly rules = new HashSet<ShapeRule>();

  void AddRules(ShapeRule rule) { 
    rules.Add(rule);
  } 

  // Judge if it meets all conditions
  boolean MeetsAllConditions(Shape shape) {
    rules.All(rule => rule.ok(shape));
  }
}

static int main() {
  // Create rules and combine them as a policy
  ShapeRule closedAreaRule = new ClosedAreaRule();
  ShapeRule triangleAnglesRule = new TrianglesAnglesRule();
  var trianglePolicy = ShapePolicy();
  trianglePolicy.AddRules(closedAreaRule);
  trianglePolicy.AddRules(triangleAnglesRule);

  Shape triangle = new Triangle();

  // Judge by a combined policy
  var isTriangle = trianglePolicy.MeetsAllConditions(triangle);
}

Enter fullscreen mode Exit fullscreen mode

High cohesive

Independent class design

  • Integrate relevant variables / methods into a class
  • Use INSTANCE variables / methods
  • Avoid using STATIC variables / methods
    • Static methods cannot access instance variable
  • Use methods to modify its instance valiables
    • Tell, Don't ask
    • Avoid using getter / setter.

Block invalid values on instance variables

  • Make sure to initialise on constructor.
  • Write input validation on the first of method.
  • Pass a parameter as a unique class.
    • Primitive type doesn't notice when you set a invalid value
    • Unique class throws compile error at that time
    • Organises the data structure and reduce the arguments
  • Set arguments and variables immutable as much as possible
    • Reassignment can put a invalid value accidentally.
  • Return new value object when you modify the instance
  • Avoid using output argument
    • It obfuscates where it modified the value
class Price {
  // immutable instance variable
  private readonly int value;

  // immutable argument
  Price(in int value) {
    // input validation
    if(value < 0) {
      throw IlligalArgumentException();
    }
    // initialisation on constructer
    this.value = value;
  }

  Price add(in Price addition) {
    // immutable variable
    readonly int addedValue = this.value + addition.value;
    // return new value object when modification
    return new Price(addedValue);
  }
}
Enter fullscreen mode Exit fullscreen mode

Factory method

Use with private constractor. Set different initial value by different factory methods. Reduce time to search the class with diffirently initicialised instances.

class MemberShip {
  const int STANDERD_FEE = 50;
  const int PREMIUM_FEE = 100;
  private readonly int monthlyFee;

  // private constractor.
  private MemeberShip(in int fee) {
    this.monthlyFee = fee;
  }

  // factory methods are static
  // you only need to investigate this class to modify either of fee
  static createStandardMemberShip() {
    return new MemberShip(STANDERD_FEE);
  }

  static createStandardMemberShip() {
    return new MemberShip(PREMIUM_FEE);
  }
}
Enter fullscreen mode Exit fullscreen mode

First class collection

Collections tends to be scattered and low cohesive.

  • Keep collections as private members
  • Operate the collection only via methods
Trolley {
  private readonly List<PurchaseItem> items;

  Trolley() {
    items = new List<purchaseItem>();
  }

  // Don't let operate the collection directly
  AddItem(PurchaseItem item) {
    items.Add(item)
  }

  // Return read only instance when you need to provide reference 
  readonly List<PurchaseItem> GetItems() {
    return items
  }
}
Enter fullscreen mode Exit fullscreen mode

Utility class

  • Consists of static variables and static methods
  • Integrate only something unrelevant to cohesion
    • crosscutting concerns e.g) logging, formatting
  • Be careful not to pack unrelevant functionalities.

Loose coupling

Proper separation

  • Pay attention to instance variables and separate into another classes.
    • Loose coupling depends on separation of concerns at all.

Proper consolidation

  • Don't believe DRY principle blindly.
  • Consider if those steps are in the same single responsibility before consolidating them.
  • You'll have complex conditional branches inside after all, if you integrate diffirent concerns.

Composition

  • Avoid using Inheritance as much as possible.
    • Sub class easily gets unexpected impacts when you modify its super class.
  • Use composition instead.

Encupsulation

  • Avoid using public blindly.
    • it leads tight coupling.
  • Consider if you can configure it as package private(internal) or private
  • May use protected when you need to use Inheritance.

Refactoring

Reduce nests

  • Early return/continue/break
  • Strategy pattern

Proper logic cohesion

  • Arrange order
  • Chunk steps by purposes
  • Extract them into methods

Conditional judgements

  • Extract it into methods(is/has/should/can...)
  • Combine multiple conditions into a combined methods

Consolidate arguments

  • Consolidate multiple arguments on methods into a class
  • Realise stamp coupling

Error handling

Catch

  • Catch only the error you need to handle
    • Catch business errors
    • Leave System errors
    • Avoid catching plain Exception without any reasons

Handling

Throw errors

  • Avoid returning error code.
  • Consider rethrowing the error when you need to deligate its handling to caller function.

Logging

  • Use logger library
    • Avoid logging into Console without any reasons
  • Use detailed error message
    • BAD:"unexpected error occured."
    • GOOD:"the name property is null."

Retry

  • Consider when you need to address network delay, DB connection, external service.
  • Configure reasonable interval and timeout.
    • interval: Exponential backoff and jitter.
    • timeout: Usually 3 ~ 5 times.
    • Should test if its truly appropriate for your system and adjust it.

Post processing

Release resource

  • Don't forget to release opened resource on finally clause.
  • e.g.) file, network connection.

Comments

You should only write something you cannot express as code.

Others

Formatting

  • 80 characters / line
  • Unify tab or space for indent
  • Better to configure auto format on IDE

Tools

  • Code Climate Quality
  • Understand
  • Resharper
💖 💪 🙅 🚩
tkrtmy1031
Takeru Tomii

Posted on April 21, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Refactoring: Moving Features Between Objects
singleresponsibilityprinciple Refactoring: Moving Features Between Objects

November 15, 2024

Techniques for Refactoring Legacy Code
refactoring Techniques for Refactoring Legacy Code

July 22, 2024

Tips to embody clean code
refactoring Tips to embody clean code

April 21, 2024