It’s important, for the future of the project, that class files are laid out in a way that they are easy to read and easy to modify and do consistently. We do this to avoid “spaghetti code” that is hard to read and modify and does not give new developers or code reviewers a good feeling about the stability of the project. This needs to be done right at the start of the project and enforced via code review and code analyzing tools. If not, it will be very costly to fix later down the road. In this article, I will show exactly how I format code classes by example. I have been formatting classes this way for as long as I can remember. I’m not sure why I started doing it this way. I think I just developed this naturally over time.
To see more examples of how my class files are formatted, check out the Spargine repository at https://bit.ly/Spargine. The information in this article is from my book “Rock Your Code: Coding Standards for Microsoft .NET” available on Amazon.
Class Header
First, let’s start off with what should be at the top of every class file. A file, in most cases, should only contain one type.
Header
At the top of every class file, should be a header, based on the rule from StyleCop, that contains the following information.
- Assembly name
- Original author name
- Last modified by name
- Last modified date
- Copyright information.
- Summary description
If you use GhostDoc (a free extension for Visual Studio), adding headers is very easy and easy to keep up to date. Simply choose “Document File Header” or “Document File” and the information will be added or updated.
Imported Namespaces
Next, all the imported namespaces should be listed. I sort the System namespaces first. Only the namespaces in use should be listed. Type Ctrl-R, and Ctrl-G to keep them cleaned up and sorted.
If you use CodeRush, as I do, I let it keep them sorted and cleaned up when I save the file.
Class Namespace
The namespace for the class should be listed next. In this example, I am using a new way of defining them by using a file-scoped namespace.
namespace DotNetTips.Spargine.Core.Network;
Class Definition
Next, let’s discuss the two parts to the class definition.
First is the documentation of the class. Describe the purpose of this class and what it does. Appropriate XML Documentation tags should be used. The more detail, the better. This is even more important later in the life of the project when a new developer might be working on it.
To make documenting class files easy, I use GhostDoc. GhostDoc will write the documentation for you, if you use good coding standards, it will add all appropriate documentation tags and descriptions. The graphic above shows the default documentation that GhostDoc writes.
After the documentation comes the definition of the class. Here are some important things to remember.
- Always use the appropriate class modifier! Most should not be public, but internal. Only use public if the type is used by other assemblies.
- Most classes should be sealed. Only leave this off if the class is intended to be inherited (most are not).
- Choose a descriptive name for the class. Avoid using acronyms unless they are well known. If inheriting a class, in most cases, the type name should include the base type name at the end. For example, if inheriting the Collection type, your class could be named PersonCollection. See the Inheriting Classes section in the Class Design chapter of my book “Rock Your Code: Coding Standards for Microsoft .NET” for more information.
- If a type is inheriting a base type, that should come next followed by any interfaces that the class needs to implement.
Constants and Fields
The next section of the class file should contain the definitions for constants, fields, and delegates.
Here are rules to follow.
- Constants can be defined as private, internal or public.
- Fields should always be private and only private. Never, ever public. If so, the type is breaking encapsulation and could introduce issues.
- If the object is new’d up as part of the definition or in the constructor, make sure to add the readonly
- Fields should include XML Documentation
Constructors
Next, the constructors for the type should be defined.
Here are some rules for constructors.
- Always use XML documentation that is descriptive and uses appropriate XML documentation tags.
- Always use the appropriate modifier for the constructor.
- Constructors should only be used to set field variables or to new up objects. DO NOT CALL other code from a constructor! This could introduce an Exception that could crash the entire application.
- Never throw an Exception from a constructor!
Events and Delegates
Next, define any events or delegates. Always include XML documentation.
Property Formatting
Next, lets discuss how I format properties. It’s very important to validate the data coming into a property, otherwise the type is breaking encapsulation and not following Object-Oriented Programming (OOP). I rarely see this done in the code I review. Usually, the only time to leave off validation is when the type is just a pass-through, meaning it’s just representing data that is not modified. Therefore, using the record type is a great choice for these classes.
- Include appropriate attributes.
- Property accessor should, in most cases, be either public or internal.
- Avoid using object as the return type. This could introduce performance issues since it might lead to boxing.
- Choose a descriptive, easy to understand name for the property, avoiding acronyms unless they are well known.
- The property getter should only return the private field that it’s associated with. Properties represent the state. Do not call other code in the getter.
- Properties should check to see if the incoming value is not the same as the one stored in the field. If it is, just return. There is no reason to set it twice since this can cause a performance issue. This is even more important if the type throws events when values are changed.
- Validate the value coming into the type based on business rules. If invalid, throw the appropriate exception such as ArgumentOutOfRangeException.
- If and only if the value is valid should it be set to the field. After that, if appropriate, fire off a changed event (not shown).
- When accessing the data for the property within the class, always use the property, not the field. This ensures that the data is validated, and any events are thrown.
The HasValue() method shown above is a Spargine extension method.
Method Formatting
I will discuss the important parts of defining and validating the parameters for methods.
Here are rules for your method.
- Always use the appropriate access modifier. Avoid public unless it’s used by an outside assembly.
- If the method does not access any class data, mark it as static. This makes it easier to use and could slightly improve performance.
- If the method returns data (a function), use the appropriate type, avoiding object since that could introduce boxing that affects performance.
- If the method is marked as async, make sure the name ends in Async.
- Validate the method parameters. Most parameters should be checked. A Boolean is the only one I can think of that does not need to be. This includes enumerations! The code above is using the extension method from Spargine called CheckIsDefined() which makes it easy to validate an enumeration.
Using Blank Lines to Organize Code
For as long as I can remember, I have been using single blank lines to organize sections of code. I don’t know about you, but I find code like this hard to read.
Since there aren’t any blank lines, all the code lines are smashed up against the line before it. Here is how I format code like this.
I will explain how I use blank lines to organize code.
- At the top of all methods should be the validation code. It should either validate the data and return a default value or throw and exception. In this case I am using extension methods in Spargine to validate the data. After the validation code I insert a blank line.
- In this method, next I am performing an action. In this case, I am downloading the data from an URL. After the call, I insert a blank line.
- Next, I validate that the return data is not null. I insert a blank line after the validation block.
- The next action is to convert the data to an object.
- First, I create the serializer and then read the stream, followed by a blank line.
- The next action is to flush the stream, followed by a blank line.
- Lastly, I return the object.
As you can see, I insert a blank line between every action. For more examples of how I format code, check out the source for Spargine: https://bit.ly/Spargine.
General Tips
- At a minimum, always add well-written documentation that is easy to understand for public and protected constructors, methods, and properties. I typically document everything! This is very important since it will show up in IntelliSense to help other developers and make the code easier to understand later in the project’s lifetime.
- Always take time when naming types and their methods and properties. Make sure they are easy to understand and avoid acronyms unless they are well known such as XML. Changing them later can be very painful and time-consuming. Make sure they are properly spelled too! Tools such as GhostDoc can help fix spelling mistakes. Most developers I know are poor spellers, including me!
- Ensure classes are organized as per StyleCop/Analyzer rules. This can be easily set up if you use CodeRush. For more information go to the Element Order section of the Assembly Layout chapter of my coding standards book.
Summary
I believe that writing classes like this look more professional. Not only for yourself but for the team too. You never know who might be looking at the code. This also makes it much easier for developers to understand, especially ones new to the team. Do you agree?
I know this might feel this is a lot to remember, but once you start formatting classes like this, it will just become second nature. Also, use extensions like GhostDoc and CodeRush (both FREE) to make other rules I mentioned very easy to identify and refactor.
I hope you will pick up a copy of “Rock Your Code: Coding Standards for Microsoft .NET” for more information. You can also use it for coding standards for your team!
For information about the CodeRush refactoring tool from DevExpress, go here: https://www.devexpress.com/products/coderush/
For information about GhostDoc from Submain, go here: https://submain.com/GhostDoc/
Please make a comment below. I’d love to hear from you.