|
| 1 | +# Preface |
| 2 | + |
| 3 | +## Rule of Thumb |
| 4 | + |
| 5 | +1. Readability first (your code should be your documentation most of the time) |
| 6 | +2. Follow IDE's auto formatted style unless you have really good reasons not to do so. (Ctrl + K + D in Visual Studio) |
| 7 | +3. Learn from existing code |
| 8 | + |
| 9 | + |
| 10 | +## References |
| 11 | + |
| 12 | +This coding standards is inspired by these coding standards |
| 13 | +* [Unreal engine 4 coding standard](https://docs.unrealengine.com/latest/INT/Programming/Development/CodingStandard/) |
| 14 | +* [Doom 3 Code Style Conventions](ftp://ftp.idsoftware.com/idstuff/doom3/source/codestyleconventions.doc) |
| 15 | +* [IDesign C# Coding Standard](http://www.idesign.net/downloads/getdownload/1985) |
| 16 | + |
| 17 | + |
| 18 | +## IDE Helper |
| 19 | + |
| 20 | +The settings that can be imported into your IDE can be found [here](https://github.com/popekim/CodingStyle/tree/master/CSharp). |
| 21 | + |
| 22 | +# I. Naming Conventions and Style |
| 23 | + |
| 24 | +1. Use Pascal casing for class and structs |
| 25 | +```C# |
| 26 | +class PlayerManager; |
| 27 | +struct PlayerData; |
| 28 | +``` |
| 29 | + |
| 30 | +2. Use camel casing for local variable names and function parameters |
| 31 | +```C# |
| 32 | +public void SomeMethod(int someParameter) |
| 33 | +{ |
| 34 | + int someNumber; |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +3. Use verb-object pairs for method names |
| 39 | +```C# |
| 40 | +public uint GetAge() |
| 41 | +{ |
| 42 | + // function implementation... |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +4. Use pascal casing for all method names except (see below) |
| 47 | +```C# |
| 48 | +public uint GetAge() |
| 49 | +{ |
| 50 | + // function implementation... |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +5. Use camel case for any non-public method. You might need to add custom Visual Studio style rule as described [here](https://stackoverflow.com/questions/40856186/naming-rule-violation) |
| 55 | +```C# |
| 56 | +private uint getAge() |
| 57 | +{ |
| 58 | + // function implementation... |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +6. Use ALL_CAPS_SEPARATED_BY_UNDERSCORE for constants |
| 63 | +```C# |
| 64 | +const int SOME_CONSTANT = 1; |
| 65 | +``` |
| 66 | + |
| 67 | +7. Use pascal casing for namespaces |
| 68 | +```C# |
| 69 | +namespace System.Graphics |
| 70 | +``` |
| 71 | + |
| 72 | +8. prefix boolean variables with `b` and prefix boolean properties with `Is` |
| 73 | +```C# |
| 74 | +bool bFired; // for local variable |
| 75 | +private bool mbFired; // for private member variable |
| 76 | +public bool IsFired { get; private set; } // for property |
| 77 | +``` |
| 78 | + |
| 79 | +9. prefix interfaces with `I` |
| 80 | +```C# |
| 81 | +interface ISomeInterface; |
| 82 | +``` |
| 83 | + |
| 84 | +10. prefix enums with `E` |
| 85 | +```C# |
| 86 | +public enum EDirection |
| 87 | +{ |
| 88 | + North, |
| 89 | + South |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +11. Prefix protected/private member variables with `m`. Use Pascal casing for the rest of a member variable |
| 94 | +```C# |
| 95 | +Public class Employee |
| 96 | +{ |
| 97 | + public int DepartmentID { get; set; } |
| 98 | + protected string mName; |
| 99 | + private int mAge; |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +12. Methods with return values must have a name describing the value returned |
| 104 | +```C# |
| 105 | +public uint GetAge(); |
| 106 | +``` |
| 107 | + |
| 108 | +13. Use descriptive variable names. e.g `index `or `employee` instead of `i` or `e` unless it is a trivial index variable used for loops. |
| 109 | + |
| 110 | +14. Capitalize every characters in acronyms only if there is no extra word after them. |
| 111 | +```C# |
| 112 | +public int OrderID { get; private set; } |
| 113 | +public string HttpAddress { get; private set; } |
| 114 | +``` |
| 115 | + |
| 116 | +15. Prefer properties over getter setter functions |
| 117 | +Use: |
| 118 | +```C# |
| 119 | +public class Employee |
| 120 | +{ |
| 121 | + public string Name {get; set;} |
| 122 | +} |
| 123 | +``` |
| 124 | +Instead of: |
| 125 | +```C# |
| 126 | +public class Employee |
| 127 | +{ |
| 128 | + private string mName; |
| 129 | + public string GetName(); |
| 130 | + public string SetName(string name); |
| 131 | +} |
| 132 | +``` |
| 133 | + |
| 134 | +16. Use Visual Studio's default for tabs. If another IDE is used, use 4 spaces instead of a real tab. |
| 135 | + |
| 136 | +17. Declare local variables as close as possible to the first line where it is being used. |
| 137 | + |
| 138 | +18. Always place an opening curly brace (`{`) in a new line |
| 139 | + |
| 140 | +19. Add curly braces even if there's only one line in the scope |
| 141 | +```C# |
| 142 | +if (bSomething) |
| 143 | +{ |
| 144 | + return; |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +20. Use precision specification for floating point values unless there is an explicit need for a `double`. |
| 149 | +```C# |
| 150 | +float f = 0.5F; |
| 151 | +``` |
| 152 | + |
| 153 | +21. Always have a `default:` case for a `switch` statement. |
| 154 | +```C# |
| 155 | +switch (number) |
| 156 | +{ |
| 157 | + case 0: |
| 158 | + ... |
| 159 | + break; |
| 160 | + default: |
| 161 | + break; |
| 162 | +``` |
| 163 | + |
| 164 | +22. If `default:` case must not happen in a `switch` case, always add `Debug.Assert(false);` or `Debug.Fail();` |
| 165 | +```C# |
| 166 | +switch (type) |
| 167 | +{ |
| 168 | + case 1: |
| 169 | + ... |
| 170 | + break; |
| 171 | + default: |
| 172 | + Debug.Fail("unknown type"); |
| 173 | + break; |
| 174 | +} |
| 175 | +``` |
| 176 | + |
| 177 | +23. Names of recursive functions end with `Recursive` |
| 178 | +```C# |
| 179 | +public void FibonacciRecursive(); |
| 180 | +``` |
| 181 | + |
| 182 | +24. Order of class variables and methods must be as follows: |
| 183 | + 1. public variables/properties |
| 184 | + 2. internal variables/properties |
| 185 | + 3. protected variables/properties |
| 186 | + 4. private variables |
| 187 | + 1. Exception: if a private variable is accessed by a property, it should appear right before the mapped property. |
| 188 | + 5. constructors |
| 189 | + 6. public methods |
| 190 | + 7. Internal methods |
| 191 | + 8. protected methods |
| 192 | + 9. private methods |
| 193 | + |
| 194 | +25. Function overloading must be avoided in most cases |
| 195 | + |
| 196 | +Use: |
| 197 | +```C# |
| 198 | +public Anim GetAnimByIndex(int index); |
| 199 | +public Anim GetAnimByName(string name); |
| 200 | +``` |
| 201 | +Instead of: |
| 202 | +```C# |
| 203 | +public Anim GetAnim(int index); |
| 204 | +public Anim GetAnim(string name); |
| 205 | +``` |
| 206 | + |
| 207 | +26. Each class must be in a separate source file unless it makes sense to group several smaller classes. |
| 208 | + |
| 209 | +27. The filename must be the same as the name of the class including upper and lower cases. |
| 210 | +```C# |
| 211 | +public class PlayerAnimation {} |
| 212 | +PlayerAnimation.cs |
| 213 | +``` |
| 214 | + |
| 215 | +28. When a class spans across multiple files(i.e. partial classes), these files have a name that starts with the name of the class, followed by a dot and the subsection name. |
| 216 | +```C# |
| 217 | +public partial class Human; |
| 218 | + |
| 219 | +Human.Head.cs |
| 220 | +Human.Body.cs |
| 221 | +Human.Arm.cs |
| 222 | +``` |
| 223 | + |
| 224 | +29. Use assert for any assertion you have. Assert is not recoverable. (e.g, most function will have `Debug.Assert(not null parameters)`) |
| 225 | + |
| 226 | +30. The name of a bitflag enum must be suffixed by `Flags` |
| 227 | +```C# |
| 228 | +public enum EVisibilityFlags |
| 229 | +{ |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +31. Prefer overloading over default parameters |
| 234 | + |
| 235 | +32. When default parameters are used, restrict them to natural immutable constants such as `null`, `false` or `0`. |
| 236 | + |
| 237 | +33. Shadowed variables are not allowed. |
| 238 | +```C# |
| 239 | +public class SomeClass |
| 240 | +{ |
| 241 | + public int Count { get; set; } |
| 242 | + public void Func(int count) |
| 243 | + { |
| 244 | + for (int count = 0; count != 10; ++count) |
| 245 | + { |
| 246 | + // Use count |
| 247 | + } |
| 248 | + } |
| 249 | +} |
| 250 | +``` |
| 251 | + |
| 252 | +34. Always use containers from `System.Collections.Generic` over ones from `System.Collections`. Using pure array is fine as well. |
| 253 | + |
| 254 | +35. Prefer to use real type over implicit typing(i.e, `var`) unless the type is obvious from the right side of assignment, or when the type is unimportant. Some acceptable var usage includes `IEnumerable` and when `new` keyword is on the same line, showing what type of object is being created clearly. |
| 255 | +```C# |
| 256 | +var text = "string obviously"; |
| 257 | +var age = 28; |
| 258 | +var employee = new Employee(); |
| 259 | + |
| 260 | +string accountNumber = GetAccountNumber(); |
| 261 | +``` |
| 262 | + |
| 263 | +36. Use static class, not singleton pattern |
| 264 | + |
| 265 | +37. Use `async Task` instead of `async void`. The only place where `async void` is allowed is for event handler. |
| 266 | + |
| 267 | +38. Validate any external data at the boundary and return before passing the data into our functions. This means that we assume all data is valid after this point. |
| 268 | + |
| 269 | +39. Therefore, do not throw any exception from inside our function. This should be handled at the boundary only. |
| 270 | + |
| 271 | +40. As an exception to the previous rule, exception throwing is allowed when switch-default is used to catch missing enum handling logic. Still, do not catch this exception |
| 272 | +```C# |
| 273 | +switch (accountType) |
| 274 | +{ |
| 275 | + case AccountType.Personal: |
| 276 | + return something; |
| 277 | + case AccountType.Business: |
| 278 | + return somethingElse; |
| 279 | + default: |
| 280 | + throw new ArgumentOutOfRangeException(nameof(AccountType)); |
| 281 | +} |
| 282 | +``` |
| 283 | + |
| 284 | +41. Prefer not to allow `null` parameter in your function, especially from a `public` one. |
| 285 | + |
| 286 | +42. If `null` parameter is used, and postfix the parameter name with `OrNull` |
| 287 | +```C# |
| 288 | +public Anim GetAnim(string nameOrNull) |
| 289 | +{ |
| 290 | +} |
| 291 | +``` |
| 292 | + |
| 293 | +43. Prefer not to return `null` from any function, especially from a public one. However, you sometimes need to do this to avoid throwing exceptions. |
| 294 | + |
| 295 | +44. If `null` is returned from any function. Postfix the function name with `OrNull`. |
| 296 | +```C# |
| 297 | +public string GetNameOrNull(); |
| 298 | +``` |
| 299 | + |
| 300 | +45. Try not to use object initializer. Use explicit constructor with named parameters instead. Two exceptions. |
| 301 | + a. When the object is created at one place only. (e.g, one-time DTO) |
| 302 | + b. When the object is created inside a static method of the owning class. (e.g, factory pattern) |
| 303 | + |
| 304 | + |
| 305 | +# II. Framework Specific Guidelines |
| 306 | + |
| 307 | +## A. XAML Controls |
| 308 | + |
| 309 | +1. Do not name (i.e, `x:name`) a control unless you absolutely need it |
| 310 | + |
| 311 | +2. Use pascal casing with prefixed `x` character for the name. |
| 312 | +```C# |
| 313 | +xLabelName |
| 314 | +``` |
| 315 | + |
| 316 | +3. Prefix the name with full control type |
| 317 | +```C# |
| 318 | +xLabelName |
| 319 | +xButtonAccept |
| 320 | +``` |
| 321 | + |
| 322 | + |
| 323 | +## B. ASP .NET Core |
| 324 | + |
| 325 | +1. When using DTO(Data Transfer Object)s for a request body for a RESTful API, make each value-type property as nullable so that model validation is automatic |
| 326 | +```C# |
| 327 | +[Required] |
| 328 | +public Guid? ID { get; set; } |
| 329 | +``` |
| 330 | + |
| 331 | +2. Validate all the requests as the first thing in any controller method. Once validation passes, all inputs are assumed to be correct. So no [required] nullable properties will be null. |
| 332 | + |
| 333 | +3. Unlike above, `[RouteParam]` will not have ? |
| 334 | +```C# |
| 335 | +public bool GetAsync([RouteParam]Guid userID) |
| 336 | +``` |
| 337 | + |
| 338 | + |
| 339 | +## C. Service/Repo Pattern |
| 340 | + |
| 341 | +1. For the DTO classes and enums that are only used internally (e.g, internal microservice or DTO between service and repo), prefix it with X. This means they are transient classes and enums |
| 342 | +```C# |
| 343 | +public sealed class XNode |
| 344 | +{ |
| 345 | +} |
| 346 | + |
| 347 | +public enum EXTransactionStatus |
| 348 | +{ |
| 349 | +} |
| 350 | +``` |
0 commit comments