You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4.4 KiB
4.4 KiB
Coding Best Practices
This document describes some typical programming pitfalls and best practices related to C# and .NET. It will grow and change as we encounter new situations and the codebase evolves.
Correctness & Business Logic
- Validate if the code adheres to what it is supposed to do. Compare the implementation with the specification and ask questions, to get clarity.
- Employ
Debug.Assert()
statements to validate program state, but never rely on them to control program flow. For example,Debug.Assert()
must never be used to validate input.
Memory / Resource Management
- C# – Rent large buffers from ArrayPool<T> and return them promptly to cut down on heap allocations and improve throughput. Have the buffers been returned to the pool? Will they be returned if an exception is thrown?
- Prefer Span<T> for stack allocated buffers.
- Double check the depth of the call stack if using async/await. Stack depth of unwinding async/await state-machines of more than 3 calls can cause performance issues.
- Resource cleanup Dispose(), Close(), or the equivalent to release files, sockets, database connections, and other external resources.
Error Handling & Reliability
- Prefer return values to throwing exceptions. Returns are strongly typed and
checked by the compiler. Consider patterns like
bool TryXYZ(..., out Result result)
instead ofResult XYZ(...)
. - Exceptions should be reserved for exceptional circumstances, like constructor failure or out-of-memory, where the calling code stands little chance of recovering. Routine, expected error conditions should not be expressed as exceptions.
- Document all exceptions that may be thrown from an API.
- Do not let undocumented exceptions escape from an API.
- Be permissive with what you accept, and strict with what you produce.
- Consider Constrained Execution Regions for .NET Framework implementations.
Async, Concurrency & Thread Safety (if applicable)
- Avoid calling Task.Result, Task.Wait() or Task.GetAwaiter().GetResult(). If needed, exercise extreme caution. These can cause deadlocks.
- When awaiting a task with the await keyword, always use Task.ConfigureAwait(false) so that the task continuation may run on any thread rather than marshalling back to the original calling thread.
Backward Compatibility
- Someone will always depend on your undocumented behaviour. It is just as much an API promise as explicitly documented behaviour.
- Use language features to deprecate/obsolete APIs, and to issue warnings for notable upcoming breaking changes.
Security Considerations
- Never log passwords, secrets, or connection strings with credentials.
- Validate inputs to avoid SQL injection, even on metadata calls.
- Are there any user inputs going into SQL or shell commands directly?
- Are secrets being logged or exposed in stack traces?
- Are TLS/certificate settings handled safely and explicitly?
- Are we sending unbounded data streams to server prior to authentication e.g. in feature extensions?
Performance & Scalability
- For major features or large PRs, always run the internal performance benchmarks or performance testsuite to determine if the new changes are causing any performance degradation.
Observability (Logging / Tracing / Metrics)
- Error messages should contain enough context to understand the state of the program and find the source code responsible for emitting it.
- Instrument changes for metrics/tracing/logging to aid future field diagnosis, debugging, and app behaviour analysis.
Unit Tests / Integration
- Have you added unit tests and integration tests?
- Unit tests should not depend on external resources. Code under unit test should permit mocked resources to be provided/injected.
- Avoid tests that rely on timing.
Configuration & Feature Flags
- Is the change considered a breaking change? If breaking change is not avoidable, has a Configuration/Feature flag been introduced for customers to revert to old behavior?
Code Coverage expectations
- Ideally these expectations should be codified into scanning tool configuration that lives in the repo.
- Specific metrics will be chosen based on realistic analysis of existing coverage and will be re-evaluated as testing matures.
Pipeline runs
- Is the CI passing? If not, then have you looked at the failures, and found the cause for failures?