Some principles for coding and refactoring
A decade of software development and engineering taught me there are two design principles that I'd argue are more important than any particular pattern or design approach:
- Code should be readable and self-explanatory.
- Each function or method (I use these terms interchangeably) should have a single, well-defined purpose.
Code should be readable
Programming languages are designed to provide human-readable abstractions of software, and, as such, code should be intelligible and relatively easy to work with.
I have seen 'data modelling' software (used to influence public policy, as it happened) that was so badly coded that everything was in a main() function that was several thousand lines long, variables weren't named descriptively and it was damn near impossible to verify whether the 'data modelling' software even worked. There weren't any documented tests indicating what the expected outputs were for any given input.
Conversely, I've seen over-engineered code that used interfaces, dependency injection and other methods of decoupling for everything, and it wasn't obvious what anything did, without tracing through layers of classes and methods to determine where something was implemented. Often it's done by developers who do it because they're told it's 'best practice', and it makes the software more maintainable and testable in theory.
The above are the extremes. In both cases, it's much harder to extend, maintain or test the software. I haven't been around long enough to know what effects over-engineering software would have on technical debt in the longer term. We should be aiming for code that is self-explanatory, where each class and method has a specific purpose that's intelligible even to a junior developer, and naming is descriptive.
Single-Responsibility Principle
There are numerous acronyms used among developers to refer to various approaches to coding patterns and software design, but I believe the one that's most important to apply is 'SRP', which refers to Single Responsibility Principle. Of course, there have been debates about what this actually means in practice, and how specifically it should be applied. I think most developers would use common sense to decide that.
If a developer follows SRP, it should result in a mostly SOLID design as a consequence, since it means we have methods that can be re-used, classes that can be extended and code that's better suited to dependency injection.
Some other principles
As a general rule, it's a good idea to follow the existing code patterns when working on an existing project, so your classes and functions/methods shouldn't look too different to the ones previously added by the other developer(s). This, of course, assumes the code is already well-written and consstent.
Variable names should be descriptive and begin with a lower-case letter, e.g.
string userName;
List<string> userList = new List<string>();
Where possible,variables should be strongly-typed instead of declaring them as 'var'.
Function names should be descriptive, and be camel-case. e.g.
MainFunction()
WriteToFile()
Avoid code duplication. Consider moving duplicated code into methods and classes that can be re-used.
Remember to remove all unused assembly references or imports during refactoring.
Comments should be added only if they're useful to developers reading the code. The code should be as self-explanatory as possible.
Values that are sensitive or likely to change should be read from appsettings or a config file, instead of being hard-coded.
Exception handling: What should a method do if it fails to execute as intended?
Console log and exception messages: Consider including these into the code where appropriate, as they really can be useful.