46 ms·
I write code bottom up. Break problem to first principles, write/reuse primitive, combine them. Fixing bugs is easy as one of underlying things doesn’t work and
by mirekrusin 4y ago
I write code bottom up. Break problem to first principles, write/reuse primitive, combine them. Fixing bugs is easy as one of underlying things doesn’t work and you can scope it down quickly and patch with test.
- lelanthran 4y agoThe problem with that is that no one, not even you, has 100% accurate foresight. IOW, you will never have your bottom layer done correctly or completely. At some top layer you're going to think "well, looks like I won't be need that function", OR, "Well, looks like I am missing a function for $FOO"/
- garethrowlands 4y agoIt doesn't have to be perfect to be a decent approach sometimes. A spare or missing function isn't necessarily that bad. It's the premise of On Lisp, for example, http://www.paulgraham.com/onlisp.html http://www.paulgraham.com/onlisp.html
- Jtsummers 4y agoThat's what refactoring/rewriting is for. You have your piece-parts, you start combining them at the next layer up. Realize some of them aren't what you need. Maybe you need more, maybe they should be combined in some fashion, or maybe their behavior should change. So you do. It's not like people (well, fools might, but who wants to be a fool?) say, "You know, I already finished that bottom layer, I should never, ever, ever change it. That would be admitting I was wrong and not psychic. I can't have that." Since most people aren't fools they will, instead, say something like, "Oops, that doesn't compose well. Time to fix it."
- lelanthran 4y ago> That's what refactoring/rewriting is for. You have your piece-parts, you start combining them at the next layer up. Realize some of them aren't what you need. Maybe you need more, maybe they should be combined in some fashion, or maybe their behavior should change. So you do. It's not like people (well, fools might, but who wants to be a fool?) say, "You know, I already finished that bottom layer, I should never, ever, ever change it. That would be admitting I was wrong and not psychic. I can't have that." Since most people aren't fools they will, instead, say something like, "Oops, that doesn't compose well. Time to fix it." I'm not saying it never gets fixed, I'm saying it's extra work compared to top-down. Top-down produces only and exactly what is needed to get the current layer to compile, run, and pass/fail. Bottom-up requires that the bottom-most unimplemented layer be written to provide at least what the next higher level needs. And since you cannot predict that perfectly all the time, you will have to come back and refine (not refactor) that layer: remove some functionality that turned out not to be needed, or add some other functionality that turns out was needed. This step is never going to happen in a top-down effort. [PS. At work I've almost always done bottom-up, because that suits the workflow constraints better: 1) Never leave commit a non-working build, 2) never do a PR for an incomplete module, 3) Always make PRs as small as possible, etc. For my own projects/hobbies/experiementation, I used to do the same thing. Now I'm experimenting with the top-down approach and my velocity is a little faster because I am never spending time removing stuff that was written.]
- karmakaze 4y agoThis is exactly my knee-jerk opposition to 'refactoring'. In many cases the cause for it isn't that the factor (seams along which the problem was separated) has changed, but rather the factor wasn't known at the time and was more like primitives composed without context or deduplicating patterns/machinery. Going top-down you'd be conceptualizing/picking/naming the factors as you go. Sometimes they change too but not at arbitrary points in layers of implementation, and this all happens sooner than later.
- mirekrusin 4y agoBottom layers have functionality that is needed. There is also reuse, dovetailing happens, you have opportunities to see new problems from lenses of already solved ones. We're using this approach in trading production system for about 5 years now and I recommend it, works very well. There is also side effect where your dependency tree is more shallow.
- lelanthran 4y ago> Bottom layers have functionality that is needed. There is also reuse, dovetailing happens, you have opportunities to see new problems from lenses of already solved ones. > We're using this approach in trading production system for about 5 years now and I recommend it, works very well. I haven't worked anywhere since 2004 that used any other approach; it's the dominant approach to development - commit code, starting from the lowest layer to the highest. My experimentation thus far using "commit code, starting from the highest level to the lowest" has been more satisfying to me in terms of velocity.
- karmakaze 4y agoWhen it comes to interface design, I draw top & down to middle. Find what interface or mechanism that is the common thing that makes the lower-level agnostic and the upper-level about applying or reapplying that mechanism in varying contexts. Some might call that thing a 'narrow waist' but I don't quite equate it to that although it's a good example. Is the thing you want a stream of bytes, sequence of messages, unsequenced messages, or priority-ordered messages? The problem with bottom-up is that you don't know the context. The problem with top-down is that you do know the context--and can leak it into what should be without. I find the latter works out better if you're aware of avoiding it. How do you avoid not having context without going top-down? An analogy in UX would be Windows vs Mac. Windows builds things then puts the UI on what they've built--in many different kinds of settings places. Mac figures out what information & controls the user should have and how to name and group them. Edit: I actually have a recent real-world example. Making a subsystem for querying and mutating things, what they are doesn't matter. We set out making an 'adjust' operation and a 'move' operation because we wanted to preserve paired decrement/increment amount for moves. We were also certain that we only needed to move between two things, or adjust many things but only of one kind. We got all the way up to the top where the public API was getting close to complete. We discovered use-cases that made sense to to more than those operations. We were able to shuffle things around and ended up with a fully-capable 'adjust' operation that can work on any number of kinds/things, and a fully-capable 'move' operation that can work on any number of kinds/things. On top of that we added checks to only do what we need now. The more general things were substantially harder to make work efficiently which is why we chose not to do it when we were sure we didn't need it. We were wrong.
- mirekrusin 4y agoI don't get what you're trying to say. You know the context. You have the whole problem in front of you. The question is about which direction you're going to solve it. For "you want a stream of bytes, sequence of messages, unsequenced messages, or priority-ordered messages?" the answer is you probably want type parametrized iterator at this level if you can ask this kind of question. To put it in other words you put more attention into trying to find underlying composition of algebras in problems than doing adhoc, inlined imperative constructions littered with if statements that you add every time some issue is discovered.