I don't agree fully to that (1). I used to do it, because it sounds like a great idea...
However, the implementation of your use case is often too subtle to get the API right to start with, and you end up having to play around and 'adjust' your API. ie, maintaining it even tho it doesn't even exists yet!
My method is now to implement a 'dirty' integration, without any care of structure or cleanliness, then when it's in place and working only then do I sit down and look at the API I would have liked to have, and implement this by mostly refactoring what is already working.
With that method, you end up with something that is clean, and more importantly does exactly what is needed an no more. There is no bits 'that could be useful later' and are never implemented, or used, or tested, and the model fits the job.
Yeah, this really reminds me of Sandi Metz's "Duplication is less expensive than the wrong abstraction." I know we aren't talking duplication specifically here, but I've eagerly built the wrong abstraction several times and it suuuuucks.
These days I'm all for the "dirty integration" first.
If you have proper functional tests, you can replace wrong abstractions at a cost.
The real issue is code abstracted bottom-up instead of proper design. Such abstractions end up being special cases and become annoying and worthless if requirements (incl. internal ones) change.
While sometimes you can design yourself into a bad end (typically by not taking a really critical requirement into consideration), this is not what often happens.
> The real issue is code abstracted bottom-up instead of proper design.
Exactly. We're (basically) saying the same thing here. I kept my comment short-and-sweet as not to get into the nitty-gritty of everyone's different work situations/requirements/stances on different testing practices/building an app vs a library or service/programming paradigm/etc.
To expand ever so slightly: "These days I'm all about 'dirty integration' first to help inform my ultimate design."
Make it work; make it right; only if proven necessary, make it fast.
Make it work is the dirty version, always comes first without thought of proper factoring or abstractions. Once working and under test, it's much easier to then make it right, which is factoring out duplication and fixing up the code to be clean and well factored. Then and only then if something is too slow and a profiler shows a problem do you make it faster.
No, you're not understanding the context in which to apply this; this isn't something you do at the app level, it's something you do at the feature level. The app is always well structured and well factored, but when adding a feature, you start dirty, get it working, then clean it up, and then optimize it if required, then move on to the next feature. This model works for all sized projects, all the time. The point of doing things dirty is to avoid premature architecture/abstraction which is a bad habit many programmers have.
Now if you have the experience to know beforehand what abstractions you need because you've done this many times, of course you can start cleaner; you do things dirty when you're unsure of what abstractions you might need and don't want to get hung up in trying to invent an abstraction before you know you need it.
It depends on your knowledge/experience levels. A very experienced develeoper's prototype would likely be much better than a junior's production-ready version.
I think this is pretty close to "the right way" (as I see it). However, I want to add one thing:
> With that method, you end up with something that is clean, and more importantly does exactly what is needed an no more. There is no bits 'that could be useful later' and are never implemented, or used, or tested, and the model fits the job.
You want a clean abstraction that gets the job done, and cleanness is better than strict minimality. If you leave big gaps in your abstraction, then it's not a good abstraction anymore, and you'll probably have problems down the road (especially once things get transferred to a support group). It's definitely a judgement call how far you should take this, however. Generally, I want to do what I can to avoid tempting future developers to put new features at the wrong level of abstraction.
If you are writing a service or library to be consumed by other programmers, you don't have much choice but to get the interface as correct as possible up front. Once it is widely use, no matter how much you might want to get all of the consumers to use a new version that fixes design flaws, its going to be extremely difficult. So in this case it is very important to put much careful consideration into the design of the API up front.
Well, at the point where you want to make a 'public' API for any sort of service, one would hope you know enough about the problem you are trying to solve to be able to provide a nice API?
Also, for a 'public' API, I would definitely go for a bit more abstraction, and would attempt at making as future proof as practical.
Of course you can ALSO fall on the other side of the fence with that by providing an API that is so 'future proofed' that they become next to unusable without another API on top! Apple was the Grand Specialist at that in the 90's.
My reply to the post two level up was more regarding /internal/ APIs, the ones you create on top of an existing library for example, to integrate into your own systems.
There's definitely a difference between what I preach, and what I frequently do in practice. It really depends what I'm building, and how well-defined are the requirements.
My method is now to implement a 'dirty' integration, without any care of structure or cleanliness, then when it's in place and working only then do I sit down and look at the API I would have liked to have, and implement this by mostly refactoring what is already working.
With that method, you end up with something that is clean, and more importantly does exactly what is needed an no more. There is no bits 'that could be useful later' and are never implemented, or used, or tested, and the model fits the job.