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)