Skip to content
Back to skills

Code Standards

ASecurity

C# naming conventions, member ordering, formatting rules, nullable reference types, memory/GC rules, test patterns, and PR standards. Use when writing, reviewing, or modifying non-trivial C# changes in this Unity project — applies to ECS systems, controllers, tests, utilities, and plugins.

  • 23 stars
  • 0 votes
  • 0 copies
  • 7 views
  • Added May 27, 2026
developmentc#expressgitapiperformance

Works with

  • api

Security analysis

A100/100

Scanned May 27, 2026

npx -y skills add decentraland/unity-explorer --skill code-standards --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Code Standards?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Code Standards
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/decentraland-code-standards/badge)](https://www.skillsdirectory.com/skills/decentraland-code-standards)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: code-standards
description: "C# naming conventions, member ordering, formatting rules, nullable reference types, memory/GC rules, test patterns, and PR standards. Use when writing, reviewing, or modifying non-trivial C# changes in this Unity project — applies to ECS systems, controllers, tests, utilities, and plugins."
user-invocable: false
---

# Code Standards & Conventions

## Sources

- `docs/code-style-guidelines.md` — Full naming and formatting rules
- `docs/standards.md` — Performance, memory, and quality standards
- `docs/branch-and-pr-standards.md` — Git workflow and PR conventions
- `CLAUDE.md` — Condensed project rules

---

## Naming Conventions

| Scope | Style | Example |
|-------|-------|---------|
| Namespaces, classes, structs, interfaces, enums, public methods/properties/fields | `PascalCase` | `BillboardSystem`, `Update()` |
| Non-public fields/properties, parameters, locals | `camelCase` | `exposedCameraData`, `cameraPosition` |
| Constants, static readonly | `ALL_UPPER_SNAKE_CASE` | `MINIMUM_DISTANCE_TO_ROTATE_SQR` |
| Interfaces | `I` prefix | `IExposedCameraData` |
| Async methods | `Async` suffix | `InitializeAsync`, `LoginAsync` |
| Events | Past tense, no `On` prefix | `ViewShowingComplete` |
| Unused parameters | `_`, `__`, `___` | `Update(float _)` |

## Member Ordering

Within a class, group members in this order:
1. Enums / delegates
2. Fields
3. Properties
4. Events
5. Methods
6. Nested classes

Within each group, order by visibility: **public → internal → protected internal → protected → private**.

**Field ordering within visibility:**
1. `const` / `static readonly`
2. `static`
3. `readonly`
4. Regular public fields
5. `[SerializeField]` fields
6. Non-public fields

**Method ordering:**
1. Constructor / setup
2. Destructor / Dispose
3. Public APIs
4. Unity callbacks
5. Internal methods
6. Protected methods
7. Private helpers (placed after the methods that call them)

### Code Example — Member Ordering

From `BillboardSystem.cs`:

```csharp
public partial class BillboardSystem : BaseUnityLoopSystem
{
    // 1. Constants first
    private const float MINIMUM_DISTANCE_TO_ROTATE_SQR = 0.25f * 0.25f;

    // 2. Readonly fields
    private readonly IExposedCameraData exposedCameraData;

    // 3. Constructor (internal visibility — see CLAUDE.md §1)
    internal BillboardSystem(World world, IExposedCameraData exposedCameraData) : base(world)
    {
        this.exposedCameraData = exposedCameraData;
    }

    // 4. Override method (Update is the system's main entry point)
    protected override void Update(float t)
    {
        // ...
        UpdateRotationQuery(World, cameraPosition, cameraRotationAxisZ);
    }

    // 5. Private helper — placed after the method that calls it
    [Query]
    private void UpdateRotation(
        [Data] in Vector3 cameraPosition,
        [Data] in Quaternion cameraRotationAxisZ,
        ref TransformComponent transform,
        in PBBillboard billboard)
    {
        // ...
    }
}
```

## Formatting

- **Indentation:** 4 spaces (no tabs)
- **Braces:** Allman/BSD style — opening brace on new line
- **`var`:** Only when type is evident from the right side
- **No LINQ** — allocates too much memory; use loops
- **String interpolation:** Use `$""` not `+` concatenation
- **Expression-bodied properties:** Same line. Expression-bodied methods: new line.
- **Unity callbacks:** Always use braces (even single-line)
- Follow [`.editorconfig`](../../../Explorer/.editorconfig) and enable "Format On Save"

## Memory & GC Rules

- Minimize GC pressure — reuse objects, use object pooling (`Utility/Pool`, `Utility/ThreadSafePool`). When you rent from a pool, the matching release belongs in the same lifecycle scope (`using` / `Dispose` / `finally`). For pooled lists held as fields, mirror every `Pool.Get()` against a `Pool.Release()` in `Dispose()` — silently dropping a rented list is a quiet leak that GCs the list reference but inflates the pool with new allocations on the next `Get()`.
- Prefer `IReadOnlyCollection<T>` / `IReadOnlyList<T>` over `List<T>` / arrays; avoid `ToList()` / `ToArray()`
- Use `Span<T>`, `Memory<T>`, `ArraySegment<T>`, `stackalloc` for slices
- Avoid boxing/unboxing — do not pass structs as interfaces, do not use `object`
- Use `StringBuilder` for string concatenation; avoid string manipulation in hot paths
- Lambdas: avoid unintentional variable captures; use `static` keyword on lambdas/local functions
- Prefer `struct` over `class` where possible; use `ref`, `ref readonly`, `in` to avoid copying
- Always call `Dispose()` or use `using`; manually dispose Unity objects implementing `IDisposable`

## Nullable Reference Types

The project is migrating to nullable reference types. ~80 of ~153 assemblies already enable it at the project level; the rest default to disabled.

### Rules for New and Modified Files

- **When modifying an existing file that lacks nullable annotations**, add proper nullable annotations as part of your change.
- **Do not add `#nullable disable`** — use it only as a last-resort escape for generated code or third-party interop.

### Annotation Rules

- **Parameters that legitimately accept null** must be typed `T?` (e.g., `string? name`).
- **Return types that may return null** must be typed `T?`.
- **Fields and properties that can be null** must be typed `T?`.
- **Never use the null-forgiving operator `!`** to silence warnings — fix the root cause instead. The only acceptable use is in test code where NSubstitute returns null proxies.

## Comments

- XML `/// <summary>` on public classes and non-obvious public methods
- Comments start uppercase, end with period
- No commented-out code
- No block `/* */` comments

## Test Conventions

- Class name: `{Feature}Tests` or `{Feature}Should`
- Method names reflect Arrange/Act/Assert intent (use `Should`, `When`)
- Body split by `// Arrange`, `// Act`, `// Assert`
- Use NUnit + NSubstitute
- High test coverage for new code

### Code Example — AAA Test Pattern

From `AvatarLoaderSystemShould.cs`:

```csharp
public class AvatarLoaderSystemShould : UnitySystemTestBase<AvatarLoaderSystem>
{
    [SetUp]
    public void Setup()
    {
        // Setup shared test state
        pbAvatarShape = new PBAvatarShape { BodyShape = BODY_SHAPE_MALE, Name = FAKE_NAME };
        IRealmData realmData = Substitute.For<IRealmData>();
        system = new AvatarLoaderSystem(world);
    }

    [Test]
    public void StartAvatarLoad()
    {
        //Arrange
        Entity entity = world.Create(pbAvatarShape, PartitionComponent.TOP_PRIORITY);

        //Act
        system.Update(0);

        //Assert
        AvatarShapeComponent avatarShapeComponent = world.Get<AvatarShapeComponent>(entity);
        Assert.AreEqual(avatarShapeComponent.BodyShape.Value, BODY_SHAPE_MALE);
        Assert.AreEqual(avatarShapeComponent.Name, FAKE_NAME);
    }

    [Test]
    public void UpdateAvatarLoad()
    {
        //Arrange
        Entity entity = world.Create(pbAvatarShape, PartitionComponent.TOP_PRIORITY);
        system.Update(0);

        //Act
        pbAvatarShape.BodyShape = BODY_SHAPE_FEMALE;
        pbAvatarShape.IsDirty = true;
        system.Update(0);

        //Assert
        ref AvatarShapeComponent avatarShapeComponent = ref world.Get<AvatarShapeComponent>(entity);
        Assert.AreEqual(avatarShapeComponent.BodyShape.Value, BODY_SHAPE_FEMALE);
    }
}
```

## PR Standards

- **Branches:** Based on `dev` branch
  - `feat/...` — new features
  - `fix/...` — bugfixes
  - `chore/...` — cleanup
  - `opti/...` — optimizations
- **PR naming:** Lowercase prefix (`feat:`, `fix:`, `chore:`, `opti:`)
- **PR description:** Generic description, technical description, QA test steps
- **PR approval:** QA review + developer review + passing builds/tests
- **Merge method:** Squash and merge
- **Commits:** Commit often as save points; PRs are squashed on merge

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.