As Forme evolves, we need consistent coding conventions to ensure maintainability and readability. This document outlines the standards expected to be followed when contributing to Forme.
These guidelines follow the general principle of "use Visual Studio defaults" and align with standard .NET coding conventions while addressing specific needs of the Forme library.
-
Braces: Use Allman style braces, where each brace begins on a new line.
-
Indentation: Use four spaces of indentation (no tabs).
-
Single-line Statements: All
if/else if/elseblocks should always use braces, even for single statements. This maintains consistency across the codebase.// correct if (foo == true) { DoSomething(); } // incorrect if (foo == true) DoSomething();
-
Line Spacing: Avoid more than one empty line at any time. Do not have two blank lines between members of a type.
-
White Space: Avoid spurious free spaces (e.g., avoid
if (someVar == 0)...). -
This Keyword: Avoid
this.unless absolutely necessary.
- 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.
- Methods: PascalCase for all method names, including local functions (e.g.,
Update(),Draw()) - Properties: PascalCase (e.g.,
Position,Rotation) - Private/Internal Fields:
- Use
_camelCasefor 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
readonlywhere possible (afterstaticwhen used together)
- Use
- 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)
Preferred Approach for Forme Types:
- For types that are part of Forme (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 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
- Method Naming Standard:
- Use
From{SourceType}for static factory methods on the target type- Example:
TargetType.FromSourceType(source)creates aTargetTypefrom aSourceType
- Example:
- Use
To{TargetType}for extension methods on the source type- Example:
source.ToTargetType()as an extension method onSourceTypereturns aTargetType
- Example:
- Helper classes should use
{SourceType}To{TargetType}naming for conversion methods- Example:
ConversionHelper.SourceTypeToTargetType(source)
- Example:
- Use
- 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
-
Visibility: Always specify visibility, even if it's the default (e.g.,
private string _foonotstring _foo).- Visibility should be the first modifier (e.g.,
public abstractnotabstract public).
- Visibility should be the first modifier (e.g.,
-
Namespace Imports:
-
Specify at the top of the file, outside of
namespacedeclarations -
Order as follows:
System.*namespaces- MonoGame types at
Microsoft.Xna.Framework.* - Third-party namespaces
-
Example:
using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Forme;
-
-
Type References:
- Use language keywords instead of BCL types (e.g.,
int, string, floatinstead ofInt32, String, Single) - This applies to both type references and method calls (e.g.,
int.Parseinstead ofInt32.Parse)
- Use language keywords instead of BCL types (e.g.,
-
Variable Declarations:
- Highly discourage the use of
var. C# is a strongly typed language, and using explicit types improves code readability. - Use
varonly 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())
- Highly discourage the use of
-
String References: Use
nameof(...)instead of"..."whenever possible and relevant.
- Core Namespace:
Formefor all platform-independent font processing types - MonoGame Namespace:
Forme.MonoGamefor all MonoGame-specific rendering types
- 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
- Public APIs: All public APIs must have XML documentation comments
- Parameter Documentation: Document all parameters with
<param>tags - Return Value: Document return values with
<returns>tags - Exceptions: Document exceptions with
<exception>tags
- Validation:
- Validate parameters using
ArgumentNullException.ThrowIfNulland similar methods where available.
- Validate parameters using
- Avoid Swallowing Exceptions: Do not catch exceptions without handling or re-throwing
// 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
}
}// 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
}
}Below is an example following our style guidelines:
ExampleClass.cs:
using System;
using System.Collections.Generic;
namespace Example;
public class ExampleClass
{
private readonly List<Item> _items;
private float _rate;
public int Count => _items.Count;
public bool IsEnabled { get; private set; }
public ExampleClass(int capacity)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(capacity);
_items = new List<Item>(capacity);
IsEnabled = true;
}
public void Update(GameTime gameTime)
{
if (!IsEnabled)
{
return;
}
UpdateItems(gameTime);
CreateItems(gameTime);
}
private void UpdateItems(GameTime gameTime)
{
// Implementation
}
private void CreateItems(GameTime gameTime)
{
// Implementation
}
}