I have decided to try and formulate what I want C# code to look like. Over the years I have accumulated a whole host of opinions that may or may not be complimentary or maybe contradict each other. The purpose of this post is to try and make sense of all of it and maybe crystallize what my style is – so that I can tell all of you how wrong you are and what you crazy kids should be doing.
Is this all my intellectual property? Hells to the no. I have read and heard a bunch of stuff and probably stolen ideas from most people, but the most important people I have listened to or read are Fred George and Greg Young. Sadly it didn’t take.
Object oriented programming
Loads of people hate it and cite typical C# code as the reason why. Yes. That code is horrible, but it isn’t OOP.
Why was Object Oriented programming invented? It was a way to make sense of large software systems. Rather than a very long list of functions that could – and did – call any other function perhaps modifying global state on the way, people were looking for a way to organise code so that changes were predictably difficult to make. This was achieved by introducing encapsulation. Private methods and fields could not be affected by external code and external code could not depend on the internal implementation of code in a class. If implemented correctly “change” means adding a new class and possibly deleting an old one.
Was this the only way people tried to solve this problem? No – functional programming was popularised, relying on pure functions and composition again offering a way to make changes in a predictable way.
I don’t mind you using C# to write in a functional style and by using composition, but if you’re creating classes – take encapsulation seriously. Encapsulation is The Thing with OOP. This means properties are evil. Think about it. Don’t put properties in the code.
But what about inheritance?
Yeah, when Borland C++ came out in 1992 the ads were full of Porche Targas that inherited from Carrera S but with the roof overridden to be missing (!) and similar. Oh – the code we would reuse. With inheritance.
Well. Barbara Liskov probably has some things to say about the Targa I suspect.
In short – don’t use inheritance unless the relationship between the classes is “is a” and the relation is highly unlikely to change.
Single responsibility principle
So you have heard about the single responsibility principle. When you see duplication you swoop in, add a new parameter and delete one of the functions. Boom!
Except what if those two functions weren’t doing the exact same thing? Their similarity was only fleeting. With the refactoring you have just made you have introduced a very hard coupling.
A method shouldn’t have more lines than three or four. Be liberal with the extract method refactoring. To “fit” in this metric, make guard clauses one line each. Put in one empty line after guard clauses and the meat of the class.
Why? What is this? Why are we counting lines now? Well code tends to attract more code as it ages and methods grow. Having a “magic number” that is The Limit helps you put in the time to refactor and thus battle the bloat before it happens.
Classes should not have more than two or three fields – as discussed earlier there will be no properties. If you feel yourself struggling trying to convince yourself to add another private field, do investigate whether or not there isn’t a new class in there waiting to break free.
Don’t be afraid to leverage the fact that private is a code level constraint, not an instance level one. I e, you can let instances of a class talk to each other and they will have access to the private fields of the sibling. This is one of the ways in which you can do actual work without properties.
Well with this whole Amish attitude to properties – how do you even deserialise json payloads or populate DTOs from the database? How do you write files?
By using the mediator pattern or writing adapters. At the edges you will find code getting less OO and more … procedural? I e anemic classes with only data, loads of members et c and verbose code that talks to very microsofty framework classes.
This is OK, and to some extent natural – but do not let it leak into the domain. Create domain and value objects for the actual processing without leaking any of the ugliness into the domain.
Avoid raw primitives
If you throw around a customer ID in your code – how do you represent it? As an int? How about an order ID? You know the drill – an ID cannot reasonably be added, subtracted, multiplied or divided and it probably can’t be <=0. And a customer ID and order ID should probably never be assignable to the same parameter.
Yes – take the time to do value objects. Resharper will generate equality and GetHashCode for you, so yes it is harder than in F# with more required boilerplate, but it is worth it.
Where the arbitrary constraint on method size really cuts into your Microsoft sample code style is in the area of conditionals. Do you even WinMain bruv? Nested if statements or case statements – what about them huh? Huh?!?
Yes. Get rid of them.
When making small classes an if statement is almost always too big. Lift the variability into its own class – a strategy.
There are two schools of unit testing. The London School and the Chicago School. The Chicago school relies on setup of instances, calling them and asserting the results. The London School relies on setting up a series of mocks, passing them into a class and then asserting that the mocks were called in the right order with the right values.
Of course – me being lazy – I prefer the Chicago school. With small classes you can just call the public methods and assert that the right results come out. If you feel the need to get inside the class to look at and validate internals – your class under test is too big or does too many things.
Creating a class
When you create a class, think about its purpose. “What’s its job?” as Fred George asks. Don’t create a class where the name ends in er or or. Find a concept that describes what the class understands.
It’s ok to spend 15 minutes drawing on paper how you see the next bit of coding to go before you write the first test and get going.
It depends. Of course it all depends. But I’ve never noticed “omg these classes are too small and have no weird dependencies- I need to refactor” so I am fairly confident in these recommendations.
The following books are useful to have around or to have read.
Some other books that are good for you as a developer: