Tips to embody clean code
Tags: refactoring, cleancode
Published: 2024-04-21 00:24:25

Use C# to consider each tip.

# Principals
1. **High-cohesive, Loose coupling**
1. Stick to "Single responsiblity" to keep 1.
1. 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) {
    ...
  }
}
```


GOOD:
```
const int DISCOUNT_AGE = 60;
for(int i = 0; i < collection.length; i++) {
  if(client[i].age >= DISCOUNT_AGE) {
    ...
  }
}
```

## 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]) {
      ...
    }
  }
}
```

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

...
```

* 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;
  }
}

```

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);
}
```

## 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;
}
```

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);
}

```

# 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);
  }
}
```

## 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);
  }
}
```

## 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
  }
}
```

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