Performance Optimization Techniques With Coding Standard Best Practices In C#

Nowadays, performance and security are very crucial aspects of business prospects. If your product is not good in performance and not secure as well, then you are certainly going to lose your reputation. Performance and security give a positive impact towards,

  • Customer trust
  • High volume of business
  • Reputation etc.

Because these two aspects are the keys to any software product, we can achieve these both in many ways. In this article, I am going to discuss the best practices of coding standards.

Let's say, we have a business requirement where every developer can write code in their own way to meet the business requirement. But what makes the difference between you and them? As per my understanding, in most cases, the developer thinks only from one angle which is the functional requirement to meet the business requirement, but remember, that's not enough. The real developer should think of multiple aspects before jumping into the implementation including both functional and non-functional aspects. Here, I mentioned one aspect, the non-functional requirement. This aspect plays a vital role along with the functional requirement. Non-functional requirement includes the following:

  • Security
  • Performance
  • Scalability
  • Extendability
  • User friendliness etc

As a developer, we have to consider the above points along with functional requirements. Going forward, I will show you a few of the best coding practices.

Coding standards help in writing  easily understandable code and maintaining consistency. I highly recommend to every software programmer or developer to follow the coding guidelines to help improve the readability of your source code and make software maintenance easier. I have listed a few best coding practices which provide:

  • Great performance
  • Minimized complexity
  • Easier maintainability etc.

Given below are a few of the best coding standard practices:

  • Assignments should not be made from within sub-expressions
  • Files should not have too many lines of code (maximum 1000 lines in a namespace)
  • Methods should not be too complex (Default value is 10).
  • Lines should not be too long (maximum length is 200)
  • Methods should not have too many parameters (Default value is 7 )
  • switch statements should not have too many case clauses (Default value is 30)
  • Control flow statements do, while, switch, if, foreach, for and try should not be nested too deeply (Default value is 3)
  • Expressions should not be too complex
  • Generic exceptions should not be ignored
  • Types should be named in Pascal case
  • switch case clauses should not have too many lines
  • Classes should not have only private constructors
  • async and await should not be used as identifiers
  • Boolean checks should not be inverted
  • Use break statements only with switch cases, don't use except switch cases.
  • TODO tags should be handled and add summary to every method in the file and Methods should not be empty
  • Enumerations Flags with zero-value members should be named None
  • Fields that are only assigned in the constructor must be readonly.
  • For Parameters with [DefaultParameterValue] attributes should also be marked with [Optional]
  • Empty default clauses in a switch should be removed
  1. Assignments should not be made from within sub-expressions

    Assignments within sub-expressions are hard to identify and make the code less readable. From the below table, we can observe that the preferable column contains == operator. It is also a common mistake to write "=" when "==" was meant to be written. Ideally, the expressions should not have side-effects.

    Example

    table

    Reason: Assignments within sub-expressions are hard to identify and make the code less readable.
    Complexity: Major

  2. Files should not have too many lines

    Problem
    A source file containing too many lines of code with too many responsibilities causes difficulty to understand it and makes it harder to maintain.

    Solution
    It's advised to break down the responsibilities into smaller pieces as methods which focus on well-defined tasks. Then, merge them at last in calling the method. Those smaller files will not only be easier to understand but easier to test also.

    Default value: 1000
    Complexity: Major

  3. Methods should not be too complex

    Problem
    Cyclomatic complexity of a function should not exceed a defined threshold. If that exceeds the limit, it causes:
    • Poor performance
    • Takes lot of time to execute the code which results in performance impact
    • Difficult to maintain the code
    • Difficult to understand

    Solution
    We can use Task Parallel Library (TPL) to overcome such complex code execution.

    Default value: 10
    Complexity: Major

  4. Lines should not be too long

    Problem
    Having too long lines of cod so that it scrolls horizontally, that makes it difficult to understand the complete code.

    Solution
    It's advised to split the lengthy line of code into some pieces which makes it easier to understand.

    Maximum length: 200
    Complexity: Minor

  5. Methods should not have too many parameters

    Problem
    A long parameters list creates a new structure to contain so many parameters.

    Solution
    Encapsulate multiple parameters into a single object.

    Ex: Instead of String fname, String lname, String address, String contact-number etc. into a object type, let's define them in a Person object containing all these fields and pass this object type as a methods input parameter.

    Default value: 7
    Complexity: Major

  6. SWITCH statements should not have too many CASE clauses

    Problem
    Usually, it is an attempt to map two sets of data. The real map structure will be more readable and maintainable and it should be used instead.

    Solution
    CASE clause content should be minimized by dividing into methods

    Maximum: 30
    Minimum: 3
    Mandatory: default case
    Complexity: Major

    table

  7. Control flow statements IF, FOR, FOREACH, DO, WHILE SWITCH and TRY should not be nested too deeply

    Problem
    Nested flow statements or multiple conditions cause the complex code which is hard to understand. Also, multiple nested flow statements or multiple conditional statements cause the performance issues.

    Solution
    Replace control flow statement conditions into multiple methods (splitting condition based code into multiple methods), and call them in the targeted area.

    Maximum: 3
    Complexity: Major

  8. Expressions should not be too complex

    Problem
    The complexity of an expression is decided by the number of ||, && and condition? if True: if false operators it contains.

    Solution

    Breakdown multiple conditions into methods and call them in the targeted area. Sample code is, as shown below:

    table

    Complexity: Major

  9. Generic exceptions should not be ignored

    If exception occurs in your code, then it's bad practice to simply ignore it. Instead, it is good to handle it properly and if you need to, you log them.

    Solution:

    table

    Complexity: Major

  10. Types should be named in Pascal case

    As per coding standards types (structure, class), name should be in Pascal case.

    table
    Complexity: Minor

  11. SWITCH case clauses should not have too many lines

    Problem
    CASE clause contains too many statements and causes difficulty in readability.

    Solution

    The content of CASE clause should be moved in a dedicated method, as shown below:

    table
    Complexity: Major
    Default threshold: 5

  12. Classes should not have only PRIVATE constructors

    Problem
    A class having only a private constructor cannot be instantiated and it seems to be worthless code.

    Solution
    Class should contain public constructors as well. Sample code is, as the following:

    table
    Complexity: Major

  13. ASYNC and AWAIT should not be used as identifiers

    async and await are reserved keywords. So, we should not use them for variable names in order to avoid the confusion. It is advisable not to use these keywords.

    table
    Complexity: major

  14. Boolean checks should not be inverted

    Problem
    Using inverted Boolean checks is costly and complex.

    Solution
    Instead, we should use the opposite Boolean comparison. Sample code is written below:

    table
    Complexity: Minor

  15. Break statements should not be used except for switch cases

    Break statement makes it difficult to read the code. Most preferable, every loop should have a single termination point. And, it's advised to use close braces for every condition and also, use else part for every if condition.

    table
    Complexity: Minor

  16. TODO tags should be handled and add summary to every method in the file and methods should not be empty

    If any method is empty or something you want to implement at a later point of time, then in such cases, use TODO tags within the method; which states that something needs to be implemented in TODO tags area. Outside of the methods, write some summary by stating the actual use of the method, so that, at a later point when you forgot, if you filter with TODO keyword, it will give you the filtered result and there it will remind you to write something. And by seeing the summary part, everyone can easily understand the purpose of the method.

    table
    Complexity: info

  17. Flags enumerations zero-value members should be named NONE

    We should use None flag for 0 value while defining ENUM and we should make a standard practice to maintain the consistency. Sample code is, as below:

    table
    Complexity: Minor

  18. Fields that are only assigned in the constructor should be read-only

    By the name itself, we can say that only read-only members should be assigned in a class constructor. If member variables are not declared as read-only whereas we are assigning their values in a class constructor, then unnecessarily we are creating confusion. So, to avoid such confusion, if you want to assign field value in a class constructor, then mark those fields as read-only, otherwise don't use fields inside class constructor which are not marked as read-only.

    table
    Complexity: Minor

  19. Parameters with [DefaultParameterValue] attributes should also be marked with [Optional]

    If the caller provides the value for a parameter at any cost, then it's useless to set the default value for a parameter. The DefaultParameterValue must be used in combination with Optional because DefaultParameterValue is non-usable without optional.

    table
    Complexity: Minor

  20. Empty DEFAULT clauses in a SWITCH should be removed

    If you use default clause in switch case, then it should contain an appropriate action. If not, then there is no meaning of using default clause.

    table
    Complexity: Minor

    Fiends, if you use the above coding standards, then really you will get a lot of benefits, as listed below:
    • Application performance will be improved.
    • Code maintainability will be easier.
    • Code re-usability can be acheived.
    • Easy to identify bugs.
    • Code complexity can be reduced, and much more...