The main thing about programming effectively isn’t rigidly following one style or another. I like to create environments in which solving (some kind of) problems becomes easier and easier. At work I’ve created a context in which the businesses’ typical kinds of problems can be more easily/sophisticatedly solved. I apply the principle of working at two levels at once. Always solving the immediate problem from the perspective of simplifying future encounters with some aspect of the requirements of the solution. This attitude slowly weathers out a nice space in which typical requirements can be accommodated. Consistency is important but so is innovation. Read Design Patterns and disuncreate your own.
At first blush, “always solving the immediate problem” may sound a lot like something like “Stakeholder wants a field to keep track of a list of statuses like ‘Potential, OnCreditHold, Angry, FriendOfTheBoss, Valued, etc.’ that they also need to be able to CRUD (Create, Read, Update and Delete). So, I need to modify the Customer table in the database to add a column ‘CustomerStatus’ (or can a Customer be both ‘Angry’ and a ‘FriendOfTheBoss’? [need an association table]). And I’m going to need to add a drop down (checked list?) to the Customer screen to allow the user to select amongst the available statuses.” And of course, that inner dialogue goes on: “And I’ll have to modify the loading of the Customer screen to populate the drop down with the available Statuses. And I’ll probably want to create a CustomerStatus table…” And so on. But that’s not only what I mean. That’s only the first part, “solving the problem”. Remember, the second part was “while also simplifying future encounters with some aspect of the requirements of the solution”.
So what does that look like? Could be something as simple as observing how my fingers shift in relation to my shoulders through my wrists as I move my type the letters “Shift-c” and “u” in the declaration of an instance of the “(Cu)stomoreStatus” object. Realizing that if I alternated between flaring my elbows out as I exhaled and drawing them in as I inhaled I might flow with my body’s motion rather than oppose it, as I type, and in this way avoid unnecessary muscular strain (RSI). That can cascade a whole number of hypotheses about the ergonomics of motion.
Or perhaps it happens at a more abstract layer. Maybe I see that I am always creating these tables in the database for things like “status” and “type” and “source” and other enumerated lists of possible states that the end users want to be able to manage (in terms of adding to the list, removing from the list, editing what’s already on the list, and selecting from the list). Perhaps I have an insight that I don’t need to keep creating new tables for each type. There’s a simplicity to that approach, but that simplicity simultaneously creates it’s own overhead. Common patterns of the structure of the program are occluded (having so many unrelated database tables does not hint to any similarity). And cascades of work needs to be reproduced. CRUD queries need be somehow conjured, classes to contain them, maybe. User interfaces need to be created to manage the adding, editing and removing.
Instead, I could create four tables. StateHeader, StateValue, StateProperty, StateParentLink. StateHeader (or state-header, or state_header, or stateHeader, or whatever you prefer) is the table that keeps track of all the different specific “states” that I am keeping track of in the database (for instance, CustomerStatus, CustomerType, OrderType, OrderStatus, EmployeeType, EmployeeStatus, etc.). StateValue is the table that actually keeps track of the “states” (for instance, ‘Potential, OnCreditHold, Angry…’) for each StateHeader. StateProperty accounts for the fact that my CustomerType table may have kept track of data (like whether a Customer of this type should receive weekly sales emails [ReceivesWeeklySalesEmail]) that has no meaning for EmployeeType. StateParentLink supports the use case where multiple “state”s are being tagged onto the Entity, such as a Customer being ‘Potential’ and ‘OnCreditHold’. Then there would be infrastructure to support all this fluently generically (could that infrastructure itself be genericized? [in c#, could you go so far as to create a language within combinatorially embedded generic types? what would the grammar of that look like?]).
Typically, deciding to implement such an idea would involve identifying initial candidates (CustomerStatus, for instance) and working out the kinks and making it a consumable generality. Then, to ensure the longevity of the whole system, I would commit to retroactively applying that pattern to all candidate code. I mean, I myself can usually remember “phases” of coding style or pattern application (Separate Tables vs. Unifying Abstraction), not that I want to, but anyone else who works on the code, it is really much easier for them if they encounter a consistent system (another apparent contradiction of my “integrity” post, and yet, really, it isn’t. I extol a consistency arrived at through contradiction, change, innovation.) It’s already going to be hard enough learning the depths of the domain. The variations of your (the developer’s) expectations. (Think of the chess masters who can remember the positions of chess pieces at a glance (of legit game positions)
Of course, as a system grows, this all becomes more difficult. It’s easy to apply a new pattern by modifying 20 pieces of code and adding another couple hundred lines elsewhere. It’s another matter entirely to modify thousands of lines of code spread across hundreds of files, ultimately potentially impacting the entire spectrum of activity within a company. Do you do it all at once, or spread it out over time? If you spread it out, are you confident you’ll get to it, or will you end up with an increasingly fractured system?
And too, I cited above the benefit to other coders of a consistent code base. But in the same spirit of simplicity there comes a point where brilliant abstractions obscure. Of course, it depends. Sometimes the language will work with the abstraction and a powerful simplicity can emerge. Other times we find ourselves against the grain. And sometimes this becomes too much. Too much “T : where T class”. But that’s just C#.
In LISP we can play.