|
One strategy I've used is to implement the simple, basic stuff first and leave the harder, more complicated stuff for later.
This accomplishes two things: First, management is happy to see "stuff" being produced. Second, it gives the customer and management time to reconsider, redesign what they think they want/need. Third, it gives me time to research and develop better techniques for the hard stuff. Fourth, the hard/complex stuff will frequently change between project start and the time it is needed.
|
|
|
|
|
The metaphor that comes to mind is a diamond cutter: you need a lot of expertise and a lot of patience to do it right. But polishing each face of a diamond takes time and perseverance and both of them are in short supply in the software world.
What is the chance of nailing perfectly the architecture the first time around? You see the pieces that can be reused and where each one fits only after you've travelled the design ladder from top to bottom and from bottom to top a number of times. On the other hand how many times do you have the chance to get back to a project and do major rework? In most cases the economics of the game forces you to leave unfinished bits and pieces and you move on with the sad taste of not having done your craft as well as you wanted.
Mircea
|
|
|
|
|
Having never actually learned to code. I just do it.
So I get an image in my mind's eye (poetic interlude) and keep also the idea that I'll have to extend it at some point; it, in this case, being just about everything. It causes a good deal of abstraction and that does increase the code size a bit . . . at least at first.
The whole thing about coding (not discussing language implementations) is that doing it right is just following the sensible path. Experience has embellished the path (not necessarily the code) to take into account what will happen (i.e., users) and what is likely to come.
I wouldn't say it fits any particular model but it does seem to work.
Ravings en masse^ |
---|
"The difference between genius and stupidity is that genius has its limits." - Albert Einstein | "If you are searching for perfection in others, then you seek disappointment. If you seek perfection in yourself, then you will find failure." - Balboos HaGadol Mar 2010 |
|
|
|
|
|
The more players, the more up front work you need to do. Can't pivot a crowd easily.
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
And if you have a crowd, you need an application framework. If there isn't one that's a good fit, you need to build it yourself. This needs to be done by a small team, some of whom are using it to build a few serious applications so that it evolves into something relatively stable before the crowd is unleashed.
|
|
|
|
|
The "framework" is the (up front) architecture; the "crowd" includes users; and all are on the same page due to the "architecture".
When everyone is responsible for their own sub-system, it's the interfaces I'm concerned about; not their CRUD framework.
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
For so long I've been an interface aficionado.
But I keep rediscovering C++ and with it generic programming, and template based metaprogramming which I find profoundly useful for generating efficient flexible code I couldn't easily make otherwise.
The powerful thing about this is that this "polymorphism" is source level, not binary (vtbl based). The problem with this is all of this "polymorphism" is source level, not binary.
The compiler checks it, but only if you use it by instantiating it and that means you might not catch errors in your code until well after your code is being used, even in production, because that part never was compiled.
So you lose a big advantage of interface based programming when you're using generic programming and template based polymorphism to implement your "interfaces" which again are source level, not binary.
I can't resist the urge to use it though. The power. The efficiency. The dark side... it beckons!
Real programmers use butterflies
|
|
|
|
|
Where angels fear to tread...
|
|
|
|
|
Marc Clifton wrote: Personally, my goal is always "under-coded" (meaning, as little code as possible), and I find that that drives a certain amount of architecture, usually during the coding, not before the coding. Ditto. Less, is easier to understand, maintain, service and makes it easier to track bugs. As far as I know, this is the gold standard.
Marc Clifton wrote: So it strikes me that the missing category:
well-architected, well-coded Seen it once, in 25 years.
Marc Clifton wrote: And by architecture, I don't mean gloriosky layers of abstractions Yeah. This bugs me. One of those three layers is SQL Server. And still people insist on adding another layer, because the docs say so. No! SQL Server is the abstraction to how stuff is stored on disc, you don't need that layer that does absolutely nothing.
Marc Clifton wrote: thousands of interfaces, DI and IoC. Use them if needed, don't use them just to have it. Only use it if needed.
Marc Clifton wrote: maximizing code re-use To clarify something else; code re-use, means calling an existing function, not to Ctrl-C, Ctrl-V it! You call the original, having one point of maintenance. Not copying the same bloody statements all over the place.
Marc Clifton wrote: Thoughts? I had many bosses; one would state what he needed, and demanded minimalism. He'd just say what you should output, from his input and that's it. How you got there, was your problem (that's 25 years ago). It worked like magic.
My last came with patterns, he'd paint this big picture of interactions, told everyone which parts to build, and off we was. His magic was a level up.
There also was two VB6 projects. Not gonna say anything about those, as the managers were as outdated as the language. I mean, really, storing 31 booleans for each member each day, and convert them to strings? That was the only person I met with the architect title.
Bastard Programmer from Hell
"If you just follow the bacon Eddy, wherever it leads you, then you won't have to think about politics." -- Some Bell.
|
|
|
|
|
Refactor, or bear the costs.
|
|
|
|
|
Marc Clifton wrote: well-architected, well-coded
is something that must be done simultaneously. Not the "architecture first" approach, not the "code as a hack" approach, but rather, while coding, considering where "architecture" can facilitate "well-coded."
But how would you? :
1) sell books
2) sell tickets to your conferences
I mean without that you're just writing software and getting stuff done.
What you need is a system that can only be described in books and at conferences.
|
|
|
|
|
raddevus wrote: What you need is a system that can only be described in books and at conferences.
It's called Code Project.
|
|
|
|
|
I wrote a new post that touches on this on the top. It's kind of related but different enough that I thought it warranted its own post.
Anyway, I agree with you that architecture is something that is done throughout the lifecycle of the project. You do want to front load your project with a design phase, but you don't freeze the design before you write the code. I think people tend to believe you do. You freeze *features* hopefully. But design is living.
Real programmers use butterflies
|
|
|
|
|
Keep in mind that in some languages that use frameworks (like C#), fewer lines of code written does not always mean fewer lines of code executed. Looking at the MSIL to compare lines of code created for some particular syntactic sugar shortcut is generally a good idea.
|
|
|
|
|
And just to complicate things I'm going to chime in to say that not only does fewer lines of code not mean more efficient, the opposite is more often the case, because naïve algorithms tend to take less code but do more work.
Because of that, the framework code is often the best choice, even for performance reasons.
I wrote a B-tree library in C# for holding millions of rows of data in a dictionary. The standard dictionary outperformed it for small sizes (of course) and kept up with it all the way until millions of rows at which point it still wasn't much worse, leaving the only advantage of my b-tree was the storing of the data in sorted order.
Don't underestimate a good framework. Lean on it hard.
Real programmers use butterflies
|
|
|
|
|
I presume to be of the same style as you do but in the last project the bank tried to design everything before starting to code, the idea was that the design would actually be the pseudo code they outsource would code from. They got me to prototype the project, what took me 3 months to complete took a team of 12, 18 months to get to production by transferring the prototype code to a design tool and getting the team to code from the tool.
The prototype was a more stable solution.
Never underestimate the power of human stupidity -
RAH
I'm old. I know stuff - JSOP
|
|
|
|
|
Architecture and code are for me on the same side of the balance, together with testing.
On the other side are the requirements, including user stories and business case.
The balance is in short: WHAT ---- HOW
With ATAM (Architecture Tradeoff Analysis Method) one verifies this balance in different ways,
- Does the architecture fulfill all the requirements?
- Does the architecture have more/less than needed? Why?
- Do the requirements define all (non) functional details needed for the architecture?
- Are there missing requirements that can be derived from the architecture?
(e.g. the ones so obvious that they are not mentioned)
With respect to the architecture, (imho) if a programmer can code it, and a test engineer can create a test specification and the requirement engineer can track the requirements, one may stop specifying the architecture. Yes, iterations will follow to improve readability and maintainability.
If the test specification cannot be derived (or seems odd etc), the architecture is probably missing something, often indicating you need to get back to the requirements / stakeholders.
So there is also an important balance in the HOW part between the architecture and the test specification, that may trigger an unbalance in the HOW - WHAT balance.
ATAM Architecture tradeoff analysis method - Wikipedia[^]
|
|
|
|
|
I had been there so many times that it just screams to me all the balancing only experience taught me. Usually learning materials are not really in touch with reality and all the ways it can variate.
My experience is that:
1 - The first step is to try anticipating the scale and lifetime of a project. This will help avoid unnecessary over or under-engineering. Patterns exist to help us, but they are often not worth the cost and can create a huge technical debt in the team.
2 - Code reuse is a double edged sword. It can both help to prevent bugs, but also create bugs. When dealing with complex systems, creating a single business rules model can be very bad. You start twisting and over abstracting so much that the codebase starts becoming very hard to understand, creates a lot of coupling and unpredictable side effects. Applying DDD here is beneficial as you separate an often "Big Ball of Organized Mess" into smaller more manageable contexts (bounded contexts). That will somewhat generate code duplication, but I believe it to be a good thing as the chances of side effects are greatly reduced and allows different business domains to evolve independently. Of course, this requires some mindfulness on the impact of a change in the big pictures, but scenarios like the one below are so much easier to deal with, that's worth the code repetition (and some times data):
Consider a hypothetical system where we have an employee class which are part of two different business domains:
1 - Payroll Management: For payroll management, you need a model that contains properties like: salary, working schedule, name, address, tax number, email. It should contain methods like calculatePayrollTaxes, calculateNetSalary, calculateIncomeTaxes, updatePersonalInfo
2 - Sales: For sales rules you likely need a whole different approach for the employee rate. For example. In a commissioned scheme, you'd need the following properties: commission rate, name, mtdCost. The methods could probably be much more focused like just having calculateCommission.
Because the P&L report needs to account for the costs of the employee, a change in payroll taxes, salary or work schedule will affect the month to date costs. But that can be kept in its entirety contained within Employee entity of the Payroll Management context, without having a huge class with all rules that involve employees. It makes a lot easier to maintain its business rules, doesn't require and endless number of abstraction layers and has much lower chances of causing side effects. It does require a top level architecture that accounts for changes that may affect other domains (for example, the case of the Sales department getting the update on mtdCost). But that can be solved simply over multiple ways that don't require direct coupling of both business context objects.
Now, with a very slim object the sales context is allowed a lot of freedom to objectively evolve objectively and more business oriented, which provides more value and leads to less conflicts. But let's say that the rules for employee names change (which is a shared and repeated property). So the Peyroll Context changes it to split that property in FirstName and LastName.
The first thing you can notice from this change is that it may impact the Employee under the Sales context. But for sales, it still doesn't care that the employee has different properties for first and last names. If you design this properly, you don't even need to care to change anything outside the employee context. Because you can:
1 - Propagate the change in the exact same way. If you use webhooks, direct rest calls or messaging to do interservice communication, you can simply concatenate those properties when you change your model. Everyone else will get those changes the same way they were getting before. You fulfil your need to change the model and don't change what doesn't need change.
2 - If you use a shared database (or tables so to speak) and monolith approach between contexts, you can still apply a similar approach, although not exactly ideal, but you can have the separation of schemas through a unified ORM layer that will account for changes in a shared data model to avoid conflicts. I would personally avoid that as there are multiple ways to (even within a single database) to keep entities separate as they are not really the same entity within the different business contexts and leverage the concept of Domain Events where all other interested domains can subscribe to, which completely eliminates the coupling trap I have fallen to many times over.
But in the end, if you have a simple project, you can simply create a "Small ball of organized mess", which will ship fast, is still easy and small enough to maintain and also adds little effort for an eventual need of refactor to scale.
Getting the sweet spot right to me, is the real challenge. And that is the thing that only experience has taught me and I really fail to contemplate a more effective way to teach that.
To alcohol! The cause of, and solution to, all of life's problems - Homer Simpson
Our heads are round so our thoughts can change direction - Francis Picabia
|
|
|
|
|
I'm almost on the same page, but I also avoid code re-use when the data is different from a functional point of view.
When 2 areas have different use cases and (functionally) different data sets, but can use the same code initially, I just duplicate the code I need and give it a name that's more appropriate for the specific area. Most of the time, the functions start to drift apart along with new business requirements. By splitting it up initially, I avoid having to add logic to differentiate between both areas, something that typically happens over time, and causes bugs when done poorly.
Also, I align my structure with whatever report / graph / property or other business object that gets identified, because that's where your design changes come from in the first place. For the same reason I avoid purely-technical abstraction layers and objects that have no representation for the end user, except for one specific case.
To me, technical abstractions are only useful as a way to divide work between teams (including external teams which I have no control over) and should be implemented, evaluated and maintained as such.
As an example, you could have 2 teams in a product, let's say UI and backend, and use 3 business critical open source packages for reporting purposes. A good structure would then have 5 critical abstractions under the hood to separate those concerns. In this case 2 layers with some rules that work for both teams, and 3 class encapsulations.
I know I sound extremely pragmatic, but the compiler doesn't care about people and doesn't care about profit, and I care tremendously about both. So that's what a good structure should add, in my personal opinion.
|
|
|
|
|
For me "good architecture" means making something easy to debug when it breaks, because it will break and I spend much too much time trying to debug cleverly architected code.
“That which can be asserted without evidence, can be dismissed without evidence.”
― Christopher Hitchens
|
|
|
|
|
You know you're doing it properly when the architecture and the code are the same thing.
|
|
|
|
|
Is a fake horse my little phony?
"I have no idea what I did, but I'm taking full credit for it." - ThisOldTony
"Common sense is so rare these days, it should be classified as a super power" - Random T-shirt
AntiTwitter: @DalekDave is now a follower!
|
|
|
|
|
Do a gallup poll and find out.
"the debugger doesn't tell me anything because this code compiles just fine" - random QA comment
"Facebook is where you tell lies to your friends. Twitter is where you tell the truth to strangers." - chriselst
"I don't drink any more... then again, I don't drink any less." - Mike Mullikins uncle
|
|
|
|
|
Neigh.
"If we don't change direction, we'll end up where we're going"
|
|
|
|
|
I thought about if furlong time - it could be colt lots of things - mane-ly though, it's equestrian of intent.
Ravings en masse^ |
---|
"The difference between genius and stupidity is that genius has its limits." - Albert Einstein | "If you are searching for perfection in others, then you seek disappointment. If you seek perfection in yourself, then you will find failure." - Balboos HaGadol Mar 2010 |
|
|
|
|
|