Note: I wrote this coding specifications for entry/mid level developers of my team, during 2013-14. It was meant to help developers write better code, and also to help peers review code more efficiently. Parts of it might be outdated now, but most of the ideas and practices still apply. Sharing the document as-is.
This document describes briefly, the coding standards & practices all the developers (including automation engineers, and anyone who writes code) should follow while developing a software application.
This document is written for any developer working with a modern object oriented programming language. These generic principles and practices apply to almost all modern OOP based programming languages. At few places it uses C#
specific examples, those have been marked specifically.
This document is not exhaustive; it describes only the most important set of guidelines to follow. This document will be updated as and when necessary.
- Readability & Maintainability – The most important aspects of a long lived codebase.
- Writing a compiling and running code is inevitable, write code that
- Others (humans) can read and understand easily
- Can be changed or extended with least side effects
- Follow “newspaper style” of writing code. Just looking at the structure and names, a fellow developer should understand the overall functionality
- Comments – try to avoid. Write code that is self-explanatory
- But, do write some comments to explain the complex parts, which cannot be understood well enough just by reading. Write xml comments if possible, so that compiler can use that.
C#
- Keep a
//TODO:
task where more work is needed
- But, do write some comments to explain the complex parts, which cannot be understood well enough just by reading. Write xml comments if possible, so that compiler can use that.
- KISS – keep it short and simple. Do not write long methods or classes. [See DRY section for more on this]
- Minimize the scope of everything. [See Scoping for more on this]
- Use functional shorthands/Lambda expressions and Linq methods for higher readability. But avoid them if the internal logic is becoming way too complex, and code is losing readability. (Linq is for
C#
) - Use ternary operator (condition ? truthy logic : falsy logic) for simple if-else
- Cache variables (create local reference) for multiple uses. e.g. define locally and use miltiple times, var lastCustId = _custService. getCustomersByPostcode(postcode). Last(). Profile. AccountDetails. Id; Improves performance and readability, reduces duplication
- Remove all kind of redundancy from code [e.g. if (isSuccess == true) return true; else return false; => return isSuccess;].
- Don’t litter the whole codebase with business logic in bits and pieces
- Core business (domain) logic should be within a single module
- Do NOT write business login in
JavaScript
, except UI validation - Do NOT write UI logic within core application classes (styles, uri, control state etc.)
- Application (or behavioural) logic should be confined in specific module (e.g. controllers for MVC applications)
- Separate out logic to maintain state (session, cache etc.) from core business logic
- Writing a compiling and running code is inevitable, write code that
- Structure – good code starts with good structure
- Follow this structure/flow for classes
- Constants, Read-only fields
- Private fields
- Properties
- Constructor
- Public methods, Internal/package-private methods
- Protected methods
- Private methods
- Group code entities logically into Projects/Packages/Modules/Folders
- Separate logical layers of application into different modules/projects (e.g. Domain, Business logic/Application, UI, data access, services, Infrastructure, Utilities etc.)
- Within folders, use sub folder hierarchically to group related/similar entities
- Use separate files for each class, struct, interface, enum etc. Name of the file and the enclosing entity must be sam5. (e.g. class Employee in Employee.cs / Employee.java)
- Follow this structure/flow for classes
- Naming – name that describes the purpose
- Name of all entities should be meaningful. They should clearly convey the purpose. Project/Package, Namespace, Classes, Interfaces, Properties, Methods, Fields, local variables and even method parameters must follow the convention
- Follow proper casing
- Namespace, Class, Interface, Property, Method, Event, Enumeration Type – must follow
PascalCasing
- Method parameter, local variables – must follow
camelCasing
- Test cases, enumeration fields, constants, configuration and resource keys – may contain Underscore_Separator
- Use underscored camel case for _privateFields [See scoping section]
- Use caps for CONSTANTS or application level variables. Pascal casing can also be used as per platform specific standards [See scoping section]
- Namespace, Class, Interface, Property, Method, Event, Enumeration Type – must follow
- Start Interface names with ‘I’, followed by a meaningful name in Pascal case
- Do NOT use Hungarian naming like strFirstName, intCount, ProductTypeEnum etc.
- Scoping - Minimize scope of everything
- Define and use everything within the minimum scope possible
- Use proper access modifier for all code entities. Use private, protected, internal/package-private, protected-internal (
C#
), public - in this precedence - Use naming that visually describes scope like _privateField, CONSTANT etc.
- Use readonly/immutable when a field’s value should not be change after initialization
- Use only get, for properties that should not be updated from outside
- Don’t forget the law of Demeter, entities should have minimum knowledge of outside world [See SOLID section for more on this]
- All the code entities (interface, class, package etc.) should have the maximum cohesion and least coupling possible. That means, the members of the entity (e.g. methods in a class) should be very closely related to each other, i.e. cohesion. Also, different entities should have the least inter-dependency as possible, i.e. coupling.
- DRY – it is not an option, DRY is the law
- Do not repeat code. Think a million times before copy-pasting code
- A piece of knowledge (logic, in other word) should exist only in one place within the codebase/application
- Reuse code as much as possible
- If a piece of code can be reused by other components, extract them out into a public method
- If a piece of code is executed multiple times within a class, extract it out into a private method. Do it, even if it has just few lines
- Always write short methods, short classes
- One single method should not have too many logic, long conditional flow or too many parameters (exception can be, for example, mapper methods for third-party/legacy entities)
- One single class should not have many methods [See SRP in SOLID]
- Methods over 20 lines and classes over 100 lines should be thought through, for possible “separation of concerns”. Separation of concerns is extremely important for a codebase to be maintainable in long term. It basically means, separate your concerns so that different entities (interface, class, package etc.) have their own independent things to take care of. For example, do NOT make the same method read data from database, and also format it for the UI. Reading data and formatting data are two different concerns.
- SOLID principles - follow religiously, they will battle out the evils of bad code
- Strictly follow Single Responsibility Principle (SRP) when writing methods, classes, modules, projects., packages or any other code entities. They all must have one and only one reason to change
- Always follow Open Closed Principle (OCP). Write classes and other code entities that are easy to extend without modification. Prefer polymorphism over conditional logic, prefer composition over inheritance, use extension methods (
C#
) for external or legacy classes, use Visitor and Decorator patterns when applicable - Remember Liskov’s Substitution Principle (LSP) when inheriting. Don’t create sub classes that violate the expected behaviour of base class
- Segregate interfaces based on logically related functionalities, follow Interface Segregation Principle (ISP). In other words, remember SRP when defining abstracts, contracts, APIs
- Try to follow Dependency Inversion Principle (DIP) whenever possible (at least when writing new code modules). Code on abstractions not on implementation, implementation should depend on abstraction not the other way round, make high level modules (logic and policy defining modules that are fundamental to the system) independent of low level modules (implementation specific details like UI, database), keep things as loosely coupled as possible. Use IoC containers for binding modules at run time
- Tests – developers miss them way too often!
- All parts of codebase has to be unit-testable
- Follow all the coding practices mentioned in this doc
- Code on abstraction (read interface), so that they can be mocked and injected
- Do not read database, files or call service within non-mockable/static classes
- Write test for all the business logic
- Try to have good coverage. But, more importantly
- Do have meaningful set of tests that confirm functional correctness of the application
- After any code change, run all the tests and fix them if required (do not alter test such a way that expected behaviour is altered, just to make them pass)
- Write related tests before code check-in [see general practices and process section]
- All parts of codebase has to be unit-testable
- Exceptions and logging – do them properly
- Always handle exceptions
- Use
Try-Catch-Finally
whenever required - Use exception filter attribute/annotation whenever possible
- Do NOT swallow exceptions. Do a
throw;
rather thanthrow ex;
- Show proper message in UI as applicable
- Do NOT use exception as control flow logic
- Do it for
JavaScript
code as well, especially when calling a service
- Use
- Log properly
- Log exception and other significant event details
- Follow the same convention for logging all over the application
- Always handle exceptions
- Cautions! – get rid of the bad practices people follow around us
- Get rid of hard coding
- Do not use magic strings, magic numbers
- If required – use xml/config/resource files for global (configurable) constants
- Constants class can be used if the values are not configurable, but use them if absolutely necessary
- Define locally used constant values as private constants within the class
- Be cautious when using
static
keyword- Use static methods if they do not use instance (object specific) values
- Do NOT use static properties (they can create deep rooted bugs)
- Do NOT use partial classes (
C#
), unless it is a asp.net web form code behind file - Avoid nested classes
- Do NOT randomly use Design Pattern names, which do not represent anything!
- Get rid of hard coding
- General practices and process
- Always design first, then code. Take time to understand the requirement, discuss with the team, design properly, and then start coding. Do NOT touch the code until the whole thing has been designed at low level
- Always get the code reviewed before checking in. NO code check-ins without thorough code. Review -> review comments -> changes per comments. (Or, commit, get reviewed, then push to master, as per team standards). Before committing, always build the project, run the unit tests, run the application and do a quick sanity
- Write related tests before code check-in and get them reviewed as well. NO code check-in without related tests
- Refactor. It’s a continuous process, do it often (when adding new code, or fixing bugs, or doing general clean up). But do it precisely and methodically, otherwise there is a high chance of breaking existing functionalities. Run tests, to make sure everything is working as expected
- Learn and use established Design Patterns, whenever they fit
- Performance – last but not the least! Always consider performance and scalability issues while writing code. [Details of good coding practices for performance & scalability are beyond the scope of this document!]
- Threading - to increase throughput of application, use multi-threading where applicable. But be very cautious and use standard and understandable constructs only, as incorrect use of them can cause critical bugs in the application which might be very difficult to debug
Note 1: C#
denotes some C# language specific constructs. If you are using some other language for development, similar constructs available for that language/platform can be used.
Note 2: (2018) Also, it does not consider latest C# features like dynamic
, async-await
and other C# 5.0+ language features.