diff --git a/CODING_GUIDELINES.md b/CODING_GUIDELINES.md new file mode 100644 index 00000000..c46159dd --- /dev/null +++ b/CODING_GUIDELINES.md @@ -0,0 +1,273 @@ +# MonoGame.Extended Coding Guidelines + +## Introduction + +As MonoGame.Extended evolves, we need consistent coding conventions to ensure maintainability and readability. This document outlines the standards expected to be followed when contributing to MonoGame.Extended. + +These guidelines follow the general principle of "use Visual Studio defaults" and align with standard .NET coding conventions while addressing specific needs of the MonoGame.Extended library. + +## Code Formatting + +1. **Braces**: Use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) braces, where each brace begins on a new line. + +2. **Indentation**: Use four spaces of indentation (no tabs). + +3. **Single-line Statements**: All `if`/`else if`/`else` blocks should always use braces, even for single statements. This maintains consistency across the codebase. + + ```csharp + // correct + if (foo == true) + { + DoSomething(); + } + + // incorrect + if (foo == true) + DoSomething(); + ``` + +4. **Line Spacing**: Avoid more than one empty line at any time. Do not have two blank lines between members of a type. + +5. **White Space**: Avoid spurious free spaces (e.g., avoid `if (someVar == 0)...`). + +6. **This Keyword**: Avoid `this.` unless absolutely necessary. + +## Naming Conventions + +### Type Naming + +- **Classes and Structs**: PascalCase (e.g., `HslColor`, `ParticleEmitter`) +- **Interfaces**: Prefix with "I" + PascalCase (e.g., `IGameComponent`, `IUpdateable`) +- **Enums**: PascalCase for type and values (e.g., `BlendMode.Additive`) +- **Type Parameters**: Single capital letter (T) or prefixed with "T" (e.g., `TKey`, `TValue`) +- **Make all internal and private types static or sealed** unless derivation is required. + +### Member Naming + +- **Methods**: PascalCase for all method names, including local functions (e.g., `Update()`, `Draw()`) +- **Properties**: PascalCase (e.g., `Position`, `Rotation`) +- **Private/Internal Fields**: + - Use `_camelCase` for internal and private instance fields (e.g., `_particles`, `_isActive`) + - Prefix static fields with `s_` (e.g., `s_defaultSize`) + - Prefix thread static fields with `t_` (e.g., `t_instance`) + - Use `readonly` where possible (after `static` when used together) +- **Public Fields**: Use PascalCasing with no prefix (use public fields sparingly; prefer properties) +- **Constants**: Use CONSTANT_CASE (e.g., `MAX_PARTICLES`, `DEFAULT_DURATION`) +- **Events**: PascalCase with "EventHandler" suffix for delegate types (e.g., `CollisionEventHandler`) + +### Extension Methods + +**Preferred Approach for MonoGame.Extended Types**: +- For types that are part of MonoGame.Extended (where we control the source code), prefer static methods within the type itself rather than extension methods +- Extension methods should primarily be used for extending types from MonoGame core or external libraries where we don't control the source + +**When Extension Methods Are Necessary**: +- **Extension Class Naming**: `{TypeName}Extensions` (e.g., `ColorExtensions`, `Vector2Extensions`) +- **One Extension Class Per Type**: Each type being extended should have its own extension class +- **No Mixed Extensions**: Don't extend multiple types in a single extension class + +### Conversion Methods + +- **Method Naming Standard**: + - Use `From{SourceType}` for static factory methods on the target type + - Example: `TargetType.FromSourceType(source)` creates a `TargetType` from a `SourceType` + - Use `To{TargetType}` for extension methods on the source type + - Example: `source.ToTargetType()` as an extension method on `SourceType` returns a `TargetType` + - Helper classes should use `{SourceType}To{TargetType}` naming for conversion methods + - Example: `ConversionHelper.SourceTypeToTargetType(source)` + +### Helper Classes + +- **Helper Class Naming**: `{Domain}Helper` (e.g., `ColorHelper`, `MathHelper`) +- **Purpose**: For utility methods that don't belong to a specific type +- **Static Only**: Helper classes should be static classes with static methods +- **No Instance State**: Helper classes should not maintain instance state + +## Declaration and Usage Guidelines + +1. **Visibility**: Always specify visibility, even if it's the default (e.g., `private string _foo` not `string _foo`). + - Visibility should be the first modifier (e.g., `public abstract` not `abstract public`). + +2. **Namespace Imports**: + - Specify at the top of the file, *outside* of `namespace` declarations + - Order as follows: + 1. `System.*` namespaces + 2. MonoGame types at `Microsoft.Xna.Framework.*` + 3. MonoGame.Extended types + - Example: + + ```csharp + using System; + using System.Collections.Generic; + + using Microsoft.Xna.Framework; + using Microsoft.Xna.Framework.Graphics; + + using MonoGame.Extended.Graphics; + using MonoGame.Extended.Particles; + ``` + +3. **Type References**: + - Use language keywords instead of BCL types (e.g., `int, string, float` instead of `Int32, String, Single`) + - This applies to both type references and method calls (e.g., `int.Parse` instead of `Int32.Parse`) + +4. **Variable Declarations**: + - Highly discourage the use of `var`. C# is a strongly typed language, and using explicit types improves code readability. + - Use `var` only when absolutely necessary, not as a general practice. + - Target-typed `new()` can only be used when the type is explicitly named on the left-hand side (e.g., `SourceType source = new()`) + +5. **String References**: Use `nameof(...)` instead of `"..."` whenever possible and relevant. + +## Code Organization + +### Namespace Structure + +- **Mirror MonoGame Structure**: Follow the same organizational patterns as the core MonoGame framework + - Graphics-related classes should be in `MonoGame.Extended.Graphics` (corresponding to MonoGame's `Microsoft.Xna.Framework.Graphics`) + - Input-related classes should be in `MonoGame.Extended.Input` (corresponding to MonoGame's `Microsoft.Xna.Framework.Input`) + - Math-related utilities should be in `MonoGame.Extended` directly (corresponding to MonoGame's `Microsoft.Xna.Framework`) + - Audio extensions should be in `MonoGame.Extended.Audio` (corresponding to MonoGame's `Microsoft.Xna.Framework.Audio`) +- **Core Namespace**: `MonoGame.Extended` for fundamental extensions and types +- **Domain-Specific Namespaces**: `MonoGame.Extended.{Domain}` for features that extend beyond MonoGame's core + - Example: `MonoGame.Extended.Particles` + - Example: `MonoGame.Extended.Tiled` + +### File and Class Organization + +- **File Layout**: One type per file with matching filename +- **Fields**: Fields should be specified at the top within type declarations +- **Member Ordering**: + - Fields (private, then protected, then public) + - Properties + - Constructors + - Methods (grouped by functionality) + - Nested types + +## Documentation + +- **Public APIs**: All public APIs must have XML documentation comments +- **Parameter Documentation**: Document all parameters with `` tags +- **Return Value**: Document return values with `` tags +- **Exceptions**: Document exceptions with `` tags + +## Error Handling + +- **Validation**: + - Validate parameters using `ArgumentNullException.ThrowIfNull` and similar methods where available. +- **Avoid Swallowing Exceptions**: Do not catch exceptions without handling or re-throwing + +## Implementation Examples + +### Extension Methods + +```csharp +// CORRECT: Dedicated extension class for Foo +public static class FooExtensions +{ + public static Bar ToBar(this Foo foo) + { + // Implementation + } +} + +// CORRECT: Dedicated extension class for Bar +public static class BarExtensions +{ + public static Foo ToFoo(this Bar bar) + { + // Implementation + } +} +``` + +### Conversion Methods + +```csharp +// CORRECT: Factory method on target type +public struct TargetType +{ + public static TargetType FromSourceType(SourceType source) + { + // Implementation + } +} + +// CORRECT: Extension method on source type +public static class SourceTypeExtensions +{ + public static TargetType ToTargetType(this SourceType source) + { + // Implementation + } +} + +// CORRECT: Helper class method naming +public static class ConversionHelper +{ + public static TargetType SourceTypeToTargetType(SourceType source) + { + // Implementation + } + + public static SourceType TargetTypeToSourceType(TargetType target) + { + // Implementation + } +} +``` + +## Example File Structure + +Below is an example following our style guidelines: + +**ExampleClass.cs:** + +```csharp +using System; +using System.Collections.Generic; + +namespace Example +{ + public partial class ExampleClass + { + private readonly List _items; + private float _rate; + + public int Count + { + get { return _items.Count; } + } + + public bool IsEnabled { get; private set; } + + public ExampleClass(int capacity) + { + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity); + + _items = new List(capacity); + IsEnabled = true; + } + + public void Update(GameTime gameTime) + { + if (!IsEnabled) + { + return; + } + + UpdateItems(); + CreateItems(); + } + + private void UpdateItems(GameTime gameTime) + { + // Implementation + } + + private void CreateItems(GameTime gameTime) + { + // Implementation + } + } +} +``` diff --git a/MonoGame.Extended.sln b/MonoGame.Extended.sln index f0df9d39..6a2e90d2 100644 --- a/MonoGame.Extended.sln +++ b/MonoGame.Extended.sln @@ -15,6 +15,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Extended.Content.P EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Extended.Tests", "tests\MonoGame.Extended.Tests\MonoGame.Extended.Tests.csproj", "{E1339B07-EB0B-454F-9803-E923843DB5C4}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "github", "github", "{0AEA5A98-E270-48AE-8BC8-21F6802ACE7E}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + CODING_GUIDELINES.md = CODING_GUIDELINES.md + CONTRIBUTING.md = CONTRIBUTING.md + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU