Skip to content

Commit 4794e53

Browse files
Add best-practices.md
1 parent 51957ef commit 4794e53

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

docs/best-practices.md

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
# Exception Handling: Best Practices
2+
3+
This document outlines best practices for managing exceptions in your C# code, emphasizing clarity, performance, and maintainability. These principles are designed to work in tandem with the CheckedExceptions Analyzer.
4+
5+
---
6+
7+
## Table of Contents
8+
9+
1. [Understand What a Bug Is](#understand-what-a-bug-is)
10+
2. [What Exceptions Represent](#what-exceptions-represent)
11+
3. [When to Avoid Exceptions](#when-to-avoid-exceptions)
12+
4. [How to Handle Exceptions Well](#how-to-handle-exceptions-well)
13+
5. [Limiting and Containing Exceptions](#limiting-and-containing-exceptions)
14+
6. [Exception Type Guidelines](#exception-type-guidelines)
15+
7. [Alternatives to Exceptions](#alternatives-to-exceptions)
16+
8. [Conclusion](#conclusion)
17+
18+
---
19+
20+
## Understand What a Bug Is
21+
22+
A **bug** is unexpected behavior. It defies user or developer expectations and can surface at any stage in development or production.
23+
24+
> **Example:**
25+
>
26+
> ```csharp
27+
> public void Foo()
28+
> {
29+
> throw new InvalidOperationException(); // Unhandled => Bug
30+
> }
31+
> ```
32+
33+
Unhandled exceptions are **bugs**. Every exception that escapes a method and isn't accounted for should be treated as a failure to anticipate or contain an error condition.
34+
35+
---
36+
37+
## What Exceptions Represent
38+
39+
Exceptions are not just errorsthey are **exceptional**. They signal that something **unexpected or unlikely** has happened that can't be handled by regular logic.
40+
41+
Examples include:
42+
43+
* Hardware faults (e.g., I/O errors, memory access violations)
44+
* Arithmetic overflows
45+
* System limitations
46+
47+
> **Note:** Exceptions differ from expected domain errors, such as validation failures, which are better modeled using `Result` objects or alternative mechanisms.
48+
49+
---
50+
51+
## When to Avoid Exceptions
52+
53+
### 1. Avoid Declaring Exceptions
54+
55+
Use `ThrowsAttribute` if needed, but avoid **over-declaring** exceptionsespecially in public APIs. It communicates unpredictability and complexity to callers.
56+
57+
### 2. Avoid Using Exceptions for Control Flow
58+
59+
Use non-throwing APIs when available:
60+
61+
> **Preferred:**
62+
>
63+
> ```csharp
64+
> if (int.TryParse(input, out var result)) { ... }
65+
> ```
66+
>
67+
> **Avoid:**
68+
>
69+
> ```csharp
70+
> int result = int.Parse(input); // Throws on failure
71+
> ```
72+
73+
---
74+
75+
## How to Handle Exceptions Well
76+
77+
### 1. Catch Exceptions Close to the Source
78+
79+
Catching exceptions near where they are thrown reduces the cost of stack unwinding and improves clarity.
80+
81+
> **Example:**
82+
>
83+
> ```csharp
84+
> try
85+
> {
86+
> SaveToDisk();
87+
> }
88+
> catch (IOException ex)
89+
> {
90+
> LogError(ex);
91+
> Retry();
92+
> }
93+
> ```
94+
95+
### 2. Avoid Top-Level Catch-Alls
96+
97+
Catching all exceptions globally hides bugs and encourages ignoring faults.
98+
99+
> **Anti-pattern:**
100+
>
101+
> ```csharp
102+
> try
103+
> {
104+
> RunApplication();
105+
> }
106+
> catch (Exception)
107+
> {
108+
> // Swallowed! Don't do this unless you're logging & exiting
109+
> }
110+
> ```
111+
112+
---
113+
114+
## Limiting and Containing Exceptions
115+
116+
### 1. Avoid Throwing More Than Four Exception Types per Method
117+
118+
Too many exceptions make it hard to reason about possible failure paths.
119+
120+
### 2. Catch and Wrap as Domain Exception
121+
122+
Aggregate multiple exception scenarios under a clearly named `DomainException`:
123+
124+
> ```csharp
125+
> catch (IOException ex) when (ex.Message.Contains("disk"))
126+
> {
127+
> throw new StorageUnavailableException("Disk error", ex);
128+
> }
129+
> ```
130+
131+
Still, prefer `Result` objects for recoverable, expected issues.
132+
133+
---
134+
135+
## Exception Type Guidelines
136+
137+
### ✅ Reuse .NET Framework Exceptions:
138+
139+
* `InvalidOperationException`
140+
* `ArgumentNullException`
141+
* `ArgumentOutOfRangeException`
142+
143+
These are conventional and understood by most developers.
144+
145+
### 🚫 Avoid Reusing Internal/System Exceptions:
146+
147+
Do **not** throw:
148+
149+
* `NullReferenceException`
150+
* `StackOverflowException`
151+
* `AccessViolationException`
152+
153+
These are meant for the runtime.
154+
155+
### 🚫 Avoid AggregateException
156+
157+
Use a custom, descriptive wrapper instead. `AggregateException` is difficult to pattern match and leads to boilerplate.
158+
159+
### 🚫 Avoid Exception Type Hierarchies
160+
161+
Stick to **flat**, explicitly named exception classes when custom exceptions are needed.
162+
163+
> **Avoid:**
164+
>
165+
> ```csharp
166+
> public class MyBaseException : Exception { }
167+
> public class FooException : MyBaseException { }
168+
> public class BarException : MyBaseException { }
169+
> ```
170+
>
171+
> **Prefer:**
172+
>
173+
> ```csharp
174+
> public class UserAccountLockedException : Exception { }
175+
> ```
176+
177+
### 🚫 Avoid Catching Base Type `Exception`
178+
179+
Too broad and may mask unexpected issues:
180+
181+
> ```csharp
182+
> catch (Exception) { ... } // Try to avoid
183+
> ```
184+
185+
---
186+
187+
## Alternatives to Exceptions
188+
189+
### 1. Prefer Result Objects for Domain Logic
190+
191+
> **Example:**
192+
>
193+
> ```csharp
194+
> public record Result(bool Success, string? Error);
195+
>
196+
> public Result RegisterUser(string name)
197+
> {
198+
> if (string.IsNullOrWhiteSpace(name))
199+
> return new(false, "Name is required");
200+
>
201+
> return new(true, null);
202+
> }
203+
> ```
204+
205+
### 2. Nullable Reference Types
206+
207+
Leverage the compilers nullable analysis to prevent `ArgumentNullException`s.
208+
209+
---
210+
211+
## Conclusion
212+
213+
Exceptions should remain what they were intended to be**exceptional**. By minimizing their use, limiting their propagation, and favoring explicit, predictable error signaling mechanisms like `Result`, you write code thats easier to maintain, safer to extend, and more aligned with user expectations.
214+
215+
Together with the **CheckedExceptions Analyzer**, these practices help ensure that exceptions remain manageable and that your software becomes less exception-prone and less buggy.

0 commit comments

Comments
 (0)