Live data from Hacker News

A Theory of Software Architecture

danuker.go.ro

171–180 of 246 posts

Re: A Theory of Software Architecture

#171
post #166

I prefer the first example. It’s more straightforward and avoids accidentally creating dependencies (for example some other code calling pluck_definition) that will be make it harder to modify when you need to add features or the API changes. Testing pluck_definition by itself is completely pointless since it does nothing on its own. This is the “test public interfaces, not private implementation” principle. Similarl…

> I also dispute that it’s hard to unit test the first function—-you would simply mock the api response and then you have a great test. How many times have you seen a test pass or fail when it shouldn’t have because the mocks didn’t match the actual code? It’s a useful technique but it has drawbacks which are not easily prevented.

You would still need to use mocking to test the imperative shell

Re: A Theory of Software Architecture

#172

Earlier quoted context omitted.

> every reference to another object is injected via a DI framework And it's injected with an interface, not a concrete class, so you have to go on a giant easter egg hunt to even figure out which class is being injected... only to eventually discover that there's only one class that implements that interface.

It's not a problem if you're using a proper IDE. Rider and IDEA will take you right to the implementation (or will display a list if there are multiple implementations) on Ctrl+Alt+B. This works on any of the parent classes/interfaces, or any inherited field or method.

> It's not a problem if you're using a proper IDE.

From another POV it's IDE vendor lock-in: make the basic programming exercise so challenging that the users have to buy your special tool to even attempt it.

Re: A Theory of Software Architecture

#173
post #135

I spend a lot of time thinking about these sorts of topics (actually, I just taught a 4 hour session yesterday that used most of the terms in this article), working with newer, less experienced developers, and trying to figure how to distill the essence of "architecture" down to something simple that everyone can start with. This is what I’ve started telling people: Use mostly functions, try to make most of them pure…

> Use mostly functions, try to make most of them pure.

This reminds me of:

> Eat food, not too much, mostly plants > > -- Michael Pollan

My new mantra: Write software, not too much, mostly functions.

Re: A Theory of Software Architecture

#174
post #135

I spend a lot of time thinking about these sorts of topics (actually, I just taught a 4 hour session yesterday that used most of the terms in this article), working with newer, less experienced developers, and trying to figure how to distill the essence of "architecture" down to something simple that everyone can start with. This is what I’ve started telling people: Use mostly functions, try to make most of them pure…

I am sort of in a similar situation meaning, I try to transfer to the team what I have learned over decades of development and now it is intuitive for me.

I think the most important principle is to "keep it simple". I mean, even if you have absolutely no idea how to put things together, just trying to limit LoC and trying to not do anything fancy is probably going to get you into better spot than any other principle alone.

"Keep it simple" has the advantage that it in itself is quite simple. Even if you are just starting you can pretty much tell simple from complicated code. You might not yet be able to make your code simple but if you honestly try you are also equipped to judge your results in an intuitive way which is essential to improve. The only way to improve is to be dissatisfied with your product and the only way to do that is to be able to judge it at least in some way. Usually the way you are dissatisfied is going to shape the direction in which you are likely to improve.

That is not the case with other principles, which oftentimes are easy to state and sometimes easy to show on a small scale of a very simple example, but give not much guidance on how to use and combine with other principles on a large scale.

For example, principle of using patterns to structure your code (we are talking GoF, PoEAA, etc.) which is frequently taught to people is right in itself (ie. whenever possible we may want to use established ideas on how to solve particular problems) but is frequently misrepresented by adepts who try to overload the application with constructs which even if correctly implemented, might have been replaced by a simpler construct. It frequently requires a lot of experience and good judgment to tell which pattern could be used in particular situation and it absolutely gives you no hint of how much is enough giving impression to novice developers that the more patterns you cram the better developer you are.

I also think that if you put "keep it simple" as your main principle, in your search on how to "keep things simple" you are probably going to discover other principles and also understand why, in the process.

I have many times seen codebase that has been thoroughly obfuscated by "pattern wannabees". I have also seen codebases made by teams that had very little knowledge of programming (like barely being able to program their way out of paper bag). Of the two, I very much prefer code written by people who don't try to be too fancy.

It takes twice as intelligent person to read the code so if some intelligent people try to be smart with their code but are misguided, it freqeuntly results in a codebase that is very hard to untangle.

With a code that is naively written, the problems tend to be simpler both to understand and to resolve.

Couple of months ago I had a discussion with a dev who was supposed to implement circuit breaker on one of the services (dunno why on one service only) and the resulting was a dozen pages of Java code that was chock full of callbacks, suppliers, optionals and what not. But something did not sit right with me because I could not for the life of me see what the code actually is doing and given the problem it was supposed to do I did not expect so much code.

So I sat down and over two hours I have simplified entire dozen pages to a single try / catch with an if in it.

Re: A Theory of Software Architecture

#175

Earlier quoted context omitted.

If that is a simple 10 line script, I agree. But if that is part of an application, you will not be able to properly test it - so you either have to do time consuming manual tests or time consuming integration tests. Splitting it into different parts allows to write unit tests and smaller integration tests. Then, when you only change the "pure" parts, you only have to verify the unit tests. That gives a HUGE producti…

You only want to test on public API, because else you cannot change the implementation without adapting your perfect unit test on implementation details. So, I prefer the first code example and do not mind some mocking (unless the extracted functions make sense on the public API).

If you take that idea/ideal to the very logical end, then you can only do system tests / acceptance tests (however you want to call it). Your tests must only be able to do what end-users do - everything else would be relying on implementation details. No unit tests at all!

...unless we take a more compositional approach and define our application to consist of many "parts" each of which has a public API and an implementation. Well, in that case I would argue that each function can be considered as such a part, having a public API (the definition of its inputs and outputs) and an implementation that can be changed while keeping the public API stable.

Over the time I came reached a conclusion: whenever I have to run some code to be sure it does what it should, I write a test instead. And every often that is a unit test. If I'm highly confident that the code will do what I expect, then I rely on very high level system tests, unless I fear that someone might break the code later by accident without the compiler (or other existing tests) being able to detect that.

Re: A Theory of Software Architecture

#176
post #91

Earlier quoted context omitted.

I think that fn x() { doStuff(); moreStuff(); forgotSomething(); } is pretty bad code, but that's probably because I consider procedures with no arguments and no return value a sign that something is poorly factored. However, fn x(y) { foo = doStuff(y); bar = moreStuff(foo); if (isSomething(bar)) { return theRest(bar) } else { return theBest(bar); } } can be a good way to separate the why from the how and clearly com…

Haven't you just described functional vs procedural programming?

It's not explicitly expressed (the functions could still execute sideeffects and e.g. mutate things), but yes, that is pretty much the style that pure functional programming enforces!.

Re: A Theory of Software Architecture

#177

Earlier quoted context omitted.

What is it that makes you think you couldn't implement ms paint in this architecture? I've seen all sorts of thing with this sort of architecture, ranging from dungeon crawlers to distributed virtual machine orchestrators, to file system browsers, etc.

E.g. I don't see how the text tool would work. Where's the transient state stored while the text is being entered, but not yet finalized? Similarly, how would drawing a line work? That is, drawing a long squiggle, that is continuously updated while drawing, but will still be undone/redone with a single Undo/Redo operation. I simply don't see how this fits the functional core + imperative shell pattern.

The imperative shell could be something like

    while not is_closed(state) {
        (mouse_activity, kbd_activity) := get_mouse_and_kbd() //blocking call
        (state, is_modified) := update(state, mouse_activity, kbd_activity)
        if (is_modified) {
            draw(state)
        }
    }
There is no reason that `update` and every function it calls cannot be pure. The state will have to include undo and redo histories, an indicator of the active mode, so that you might be in one mode while you're entering text, a different mode while you're dragging out a line or a shape, etc. It will include information about what drawing tool you have selected and what colors you've chosen for your ink and eraser. All that data will be used by the draw procedure to render the correct view of the state.

The one issue here is that once your state gets to a certain level of complexity, as it certainly does in ms paint, it's not going to be performant without immutable "persistent" data structures, which are either not available or not widely used in python/js/ruby/c.

If you're thinking i haven't achieved the goal here because the draw procedure is big and complicated and imperative, then maybe we need to replace

    draw(state)
with

    view := render(state)
    draw(view)
where render is pure and does almost all the work. But I'll leave thinking about what render looks like as an exercise for the reader (or to be described by an HN user who knows more about graphics programming than I do)

Re: A Theory of Software Architecture

#178

Earlier quoted context omitted.

It's not a problem if you're using a proper IDE. Rider and IDEA will take you right to the implementation (or will display a list if there are multiple implementations) on Ctrl+Alt+B. This works on any of the parent classes/interfaces, or any inherited field or method.

Yeah this is what I was thinking. I pity the people who navigate java projects without ctrl+alt+b. What an existence that must be.

I pity the people who navigate java projects.

Re: A Theory of Software Architecture

#179
post #154

Earlier quoted context omitted.

It's not a problem if you're using a proper IDE. Rider and IDEA will take you right to the implementation (or will display a list if there are multiple implementations) on Ctrl+Alt+B. This works on any of the parent classes/interfaces, or any inherited field or method.

It’s less of a problem but it’s still a problem because it forces people to have to keep more context in addition to what they’re really working on. Even experienced developers with the latest IDEs are slowed down when the culture encourages layers of gratuitous complexity, and the gains offered by advanced tooling are often cancelled out by people seeing that tooling as reducing the counter pressure against adding m…

Agreed, it’s not an insurmountable problem, but it’s still a hassle that gets in the way. Something that should be one click becomes 2 or 3 clicks and only works 85% of the time. For example, there might be one real implementation and one (or more) test stubs and that slows you down.

Re: A Theory of Software Architecture

#180
post #135

I spend a lot of time thinking about these sorts of topics (actually, I just taught a 4 hour session yesterday that used most of the terms in this article), working with newer, less experienced developers, and trying to figure how to distill the essence of "architecture" down to something simple that everyone can start with. This is what I’ve started telling people: Use mostly functions, try to make most of them pure…

> Use mostly functions, try to make most of them pure. This reminds me of: > Eat food, not too much, mostly plants > > -- Michael Pollan My new mantra: Write software, not too much, mostly functions.

"Not too much" is interesting. If I understand you correctly, you can write "too much software". I can think of at least three ways - bad architecture, too little abstraction forcing repetition, and just bad writing.

Did you have something else in mind here?

Post reply on HN