Use C# to consider each tip.
Principals
- High-cohesive, Loose coupling
 - Stick to "Single responsiblity" to keep 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." 
 - BAD:
 
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
 
    
Top comments (0)