-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: add page about coding guidelines
- Loading branch information
Showing
1 changed file
with
186 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
# Coding Guidelines | ||
|
||
Ayaka has several coding guidelines one should follow when contributing to the project. These guidelines are here to ensure that the codebase is consistent and maintainable. | ||
|
||
Most of them are based on Visual Studio's default settings, but some are based on my style of coding. | ||
|
||
## EditorConfig | ||
|
||
Ayaka uses an `.editorconfig` file that covers most of the coding style. | ||
|
||
This file is automatically used by Visual Studio and other editors to ensure that the code is formatted correctly. | ||
|
||
::: info | ||
The `.editorconfig` isn't flawless, but should give you a good starting point to what I expect. | ||
::: | ||
|
||
## Code Style | ||
|
||
### File Headers | ||
|
||
The following file header should be present in every C# code file: | ||
|
||
```csharp | ||
// Copyright (c) Raphael Strotz. All rights reserved. | ||
``` | ||
|
||
### Naming | ||
|
||
The following naming conventions should be followed: | ||
|
||
* Interfaces: `I` prefix and `PascalCase` (e.g. `IInterface`) | ||
* Non-Interface types (classes, structs, enums, delegates and namespaces): `PascalCase` (e.g. `ClassName`) | ||
* Constant fields: `PascalCase` (e.g. `ConstantName`) | ||
* Static readonly fields: `PascalCase` (e.g. `StaticReadonlyFieldName`) | ||
* Public symbols (properties, methods and events): `PascalCase` (e.g. `PublicSymbol`) | ||
* Public, internal and protected readonly fields: `PascalCase` (e.g. `ReadonlyFieldName`) | ||
* Protected fields: `camelCase` (e.g. `protectedField`) | ||
* Private and private readonly fields: `_` prefix and `camelCase` (e.g. `_privateField`) | ||
* Parameters: camelCase (e.g. `parameterName`) | ||
* Local variables: camelCase (e.g. `localVariableName`) | ||
|
||
### Spaces | ||
|
||
Use spaces instead of tabs! (screw them tabs) | ||
|
||
* Use 4 spaces for indentation | ||
* Indent block content, switch labels, case contents and labels | ||
* Use space after a comma (e.g. parameters) | ||
* Use space after keywords in control flow statements | ||
* Use space after a semicolon in `for` statements | ||
* Use spaces before and after binary operators | ||
|
||
But in most other cases, spaces are not required, especially the uncessary ones at the end of a line. | ||
|
||
### Newlines | ||
|
||
Newlines should be added in following cases: | ||
|
||
* End of a file | ||
* Before an opening brace (Allman style) | ||
* Before the `else` keyword | ||
* Before the `catch` keyword | ||
* Before the `finally` keyword | ||
* Before the members of an anonymous type (e.g. no `new { foo = "bar" }`) | ||
* Before the members of an object initializer (e.g. no `new Foo { Bar = "baz" }`) | ||
* Between query expression clauses (e.g. `from`, `where`, `select`) | ||
|
||
### Braces | ||
|
||
Generally saying, braces (e.g. `{ }`) are preferred for readability. | ||
|
||
There are some exceptions to this rule, such as: | ||
|
||
* Using statements, unless the explicit scope is required | ||
* Single-line statements of simple nature (e.g. method with a simple return statement) | ||
|
||
Additionally, braces should always be placed on a new line (Allman style) and the containing block must be properly indented. One exception to this rule are consecutive `using` statements, which can be placed on the same line without them having to be nested. | ||
|
||
### Parentheses | ||
|
||
Parentheses should be used in the following cases: | ||
|
||
* Arithmetic binary operators (e.g. `a + (b * c)` over `a + b * c`) | ||
* Relational binary operators (e.g. `(a < b) == (c > d)` over `a < b == c > d`) | ||
* Other binary operators (e.g. `a || (b && c)` over `a || b && c`) | ||
|
||
### Usings | ||
|
||
Namespace imports should be placed after the file's namespace declaration. | ||
|
||
The following ordering rules should be followed: | ||
|
||
* `System.*` namespaces should be placed before other namespaces | ||
* Namespaces should be sorted alphabetically | ||
|
||
### Namespaces | ||
|
||
Namespace declaration should be file-scoped and always match the folder structure of the file. | ||
|
||
::: tip GOOD | ||
|
||
```csharp | ||
namespace Ayaka.Nuke; | ||
|
||
public class Foo | ||
{ | ||
} | ||
``` | ||
|
||
::: | ||
|
||
::: danger BAD | ||
|
||
```csharp | ||
namespace Ayaka.Nuke | ||
{ | ||
public class Foo | ||
{ | ||
} | ||
} | ||
``` | ||
|
||
::: | ||
|
||
### Modifiers | ||
|
||
Modifiers should always be added, except for interface members. | ||
|
||
The following ordering rules should be followed: | ||
|
||
1. Access modifiers: `public` > `private` > `protected` > `internal` > `file` | ||
2. Remaining modifiers: `static` > `new` > `abstract` > `virtual` > `sealed` > `readonly` > `override` > `extern` > `unsafe` > `volatile` > `required` > `async` | ||
|
||
### Language keywords vs BCL types | ||
|
||
Use language keywords like `int` over `Int32`, `string` over `String`, etc., regardless of whether it's for a local variable, method parameters, class members or when accessing static members of said types. | ||
|
||
### 'this.' qualifier | ||
|
||
Do not use `this.` to qualify member access unless it's necessary for disambiguation. | ||
|
||
### 'var' usage | ||
|
||
Use `var` as long as it is easy to guess its type either from the variable assignment or variable name and context. | ||
|
||
### Variable declaration | ||
|
||
The following rules should be followed when declaring variables: | ||
|
||
* Declare one variable per line, unless you deconstruct a tuple or a similar construct | ||
* Deconstruct tuples when possible | ||
* Use the simple `default` keyword instead of `default(T)` when possible | ||
* Use the implicit object creation (e.g. `Foo obj = new();`) when possible | ||
* Inline variable declaration when applicable (e.g. `if (int.TryParse("123", out var result))`) | ||
|
||
### Expressions | ||
|
||
The following rules should be followed regarding expressions: | ||
|
||
* Use `switch` expressions when possible | ||
* Use expression bodies for single line methods and operators | ||
* Use expression bodies for operators, properties, indexers, accessors and lambdas | ||
* Do not use expression bodies for constructors and multi line methods or operators | ||
* Use pattern matching when possible (e.g. `if (o is { Bar: > 0 })` over `if (o != null && o.Bar > 0)`) | ||
* Use pattern matching with assignment over `is` with cast (e.g. `if (o is int i) { ... }` over `if (o is int) { int i = (int)o; ... }`) | ||
* Use pattern matching over `as` with `null`check (e.g. `if (o is Foo foo)` over `if (o as Foo != null)`) | ||
* Use the `not` keyword over `!` when pattern matching (e.g. `if (o is not Foo)` over `if (!(o is Foo))`) | ||
* Use the extended property pattern when pattern matching nested properties (e.g. `if (o is Foo { Bar: { Baz: > 0 } })` over `if (o is Foo { Bar.Baz: > 0 })`) | ||
|
||
### Null-checking | ||
|
||
The following rules should be followed regarding null-checking: | ||
|
||
* Use the null-conditional operator `?.` when possible | ||
* Use coalescing operator `??` when possible (e.g. `var foo = bar ?? "default"` over `var foo = bar != null ? bar : "default"`) | ||
* Use `is null` over `== null` | ||
|
||
### Others | ||
|
||
* Prefer primary constructor (that one is controversial, maybe I'll change my mind someday) | ||
* Prefer index and range operators over `Substring`, `Take`, `Skip`, etc. | ||
* Discard variables using `_` if not used (e.g. `var (id, firstName, _) = GetPerson();` whereas `GetPerson` returns a tuple `(int, string, string)`), | ||
|
||
### Unnecessary code | ||
|
||
Try to avoid unnecessary code! I hate useless clutter in my codebases. |