1. Introduction
"Clean code" isn't some sacred cow, it's a real survival tool for a programmer. In any OOP project, even the most beautiful one, you'll quickly end up with tons of classes, fields, methods, tricky connections... If you mess up the aesthetics and structure, in a week your own code turns into an unsolvable quest. You can read more about this "fight for survival" in Robert Martin's "Clean Code" — it's all about these battles.
Style in code isn't about "whatever you like", it's about making life easier for everyone:
- Your coworker (or you in a week) can quickly figure out what's going on.
- Mistakes (especially architectural ones) are obvious right away.
- The code is easier to maintain and you make fewer mistakes.
Let's see how to make OOP code that reviewers, teammates, and even linters will love.
2. Names: your first line of defense
Naming classes
Classes in C# are usually named in PascalCase (each new word starts with a capital letter, like MyNewClass) and the name should clearly answer the question "What is this?". Names should be nouns!
public class StudentAccount { /* ... */ }
public class InvoiceGenerator { /* ... */ }
Bad:
class doMagic { ... } // Bad: What kind of magic? PascalCase is broken.
Naming methods
Methods — also in PascalCase, but here it's better to use verbs + the object of the action:
public void PrintReport() { ... }
public string GetFormattedName() { ... }
Methods should reflect an action (Print, Get, Save, Calculate, etc.), so when you read it you know what will happen.
Naming fields and properties
Fields are usually private, named with a lowercase letter in camelCase, often with an underscore:
private int _count;
private Student _owner;
Properties — PascalCase, since they're part of the class's public interface:
public int Balance { get; set; }
Variables
Local variables — camelCase, as short and clear as possible for the context:
string inputName;
int studentCount;
And please, variables like a1, result2, something — only if you want to create a quest for yourself next month.
3. Organizing class structure
Putting class members in the right order makes navigation easier and helps you quickly understand what comes after what.
Usually, it's like this:
- Constructors
- Properties
- Methods
- Nested types (enum, class, etc.)
Example:
public class Student
{
// --- Fields ---
private string _name;
// --- Constructor ---
public Student(string name)
{
_name = name;
}
// --- Properties ---
public string Name
{
get => _name;
set => _name = value;
}
// --- Methods ---
public void PrintInfo()
{
Console.WriteLine($"Name: {_name}");
}
}
It's handy to separate these "blocks" with comments (// --- Methods ---), especially in big classes. JetBrains Rider, Visual Studio, and other IDEs let you quickly collapse/expand sections.
4. Comments and documentation
Comments are good. But it's bad if you overuse them or write "explanations for confusing code" when you could just rewrite the code!
A good comment explains "why", not "what".
// Using Guid as a unique identifier because the system is distributed
public Guid Id { get; set; }
Documenting methods, classes, and properties
Use xml-documentation for classes and public methods. IDEs will show these descriptions when you hover over them.
/// <summary>
/// Represents a university student.
/// </summary>
public class Student
{
/// <summary>
/// Student's name.
/// </summary>
public string Name { get; set; }
}
What NOT to comment
- Simple stuff (i++ // increment i by 1).
- Poorly named variables ("// something happens here" — yeah, but what?).
5. Divide and conquer
Small classes and methods
Golden rule: one class — one responsibility (see Single Responsibility Principle). If your Student class handles grades, email processing, and schedule management — something's off.
- Classes up to 300-400 lines — that's fine. More — time to think.
- Methods up to 15-20 lines — readable. Exceptions happen if it's a handler for a big case.
Example of a "bloated" method:
public void Process()
{
// Notify client
// Save changes
// Send email
// Write logs
// ... (15 steps)
}
Better:
public void Process()
{
NotifyClient();
SaveChanges();
SendEmail();
LogActivity();
}
Each step is moved to a separate private method, the code is shorter and easier to test.
6. Useful tips
Visual structure: formatting, indents, blank lines
IDEs can auto-format code nicely (Ctrl+K, D in Visual Studio, Ctrl+Alt+L in Rider), but you still need to know the principles.
- INDENTS — 4 spaces. Not tabs, not 2 spaces.
- BLANK LINES — separate methods from each other, fields from properties, properties from methods.
- BRACES always on a new line for classes and methods (Allman style):
public class Test
{
public void Print()
{
Console.WriteLine("Hello");
}
}
"Strong" and "weak" class members: access modifiers
Always try to make everything as closed as possible: only open up what really needs to be accessible from outside. If a field or method is only needed inside the class — make it private. Only if subclasses need it — protected. public — only for contracts.
Bad:
public string ConnectionString; // Anyone can change it!
Better:
private string _connectionString;
public string ConnectionString
{
get => _connectionString;
private set => _connectionString = value;
}
Using auto-properties
With auto-properties and init-only setters, writing "manual" properties is just not cool anymore.
Example:
public string Name { get; set; } // Awesome!
public int Age { get; init; } // Only for initialization, safer.
And if you need calculated properties:
public string FullName => $"{FirstName} {LastName}";
Encapsulation and getters/setters
If a property has some business logic for changing or controlling it, use a private field + public getter/setter with logic.
private int _grade;
public int Grade
{
get => _grade;
set
{
if (value < 0) _grade = 0;
else if (value > 100) _grade = 100;
else _grade = value;
}
}
This way you won't let yourself or others accidentally "break" the object.
7. More useful tips
Don't be afraid of interfaces and abstractions
Interfaces are for handy contracts, testing, and expanding your app.
Bad:
- an interface with one method that nobody uses;
- an interface that's only implemented by one class.
- an interface used by 2+ classes;
- an interface for abstracting external systems (like for logging, data storage).
Write code so it's easy to test
One sign of good OOP code is testability.
- Don't make methods that depend on global variables or static fields for everything.
- Don't be afraid of dependency injection — via constructor parameters (Dependency Injection).
- Separate calculations (logic) and user interaction (input/output). This makes not just testing easier, but also future changes.
Lifehacks and anti-patterns for beginners
- Don't write "God classes" (God Object) that do everything.
- Don't use "magic numbers" without explanation (if (status == 42) — why 42?).
- Don't overuse inheritance just for the sake of inheritance — sometimes composition is better (a class with fields of other classes, not a subclass).
- Don't write complicated methods 100 lines long — they're impossible to test and understand.
- Always leave room for extension (open/closed principle).
8. What bad and good code looks like
Bad example:
class s // Bad: class name is lowercase.
{
public int a; // pointless, bad name
public void m() // Bad: method name is one letter.
{
Console.WriteLine(a);
// Bad: it's not clear what the method does.
}
}
Good example:
// Represents a student.
public class Student
{
// Student's age.
private int _age;
public int Age
{
get => _age;
set => _age = value < 0 ? 0 : value;
}
// Prints info about the student.
public void PrintInfo()
{
Console.WriteLine($"Student age: {Age}");
}
}
GO TO FULL VERSION