xwowsersx 8 days ago

What I've noticed over the years is that software engineers tend to sometimes mistakenly believe that when they are, for example, modeling some domain that they are involved in some sort of Aristotelian categorization exercise wherein they are trying to capture the true essence of some pure Platonic form. In reality, whenever anything comes under the umbrella of software, we are doing it not because we are interested in some ontological sense of the thing under consideration, but because it has some usefulness for a particular line of business. This I think is the key thing. There are unifications and abstractions that make sense in a pure theoretical sense, but that serve no purpose when it comes to software that is meant to serve some practical end.

  • ParetoOptimal 8 days ago

    There is a balance here.

    Good domain modeling can enable making illegal states unrepresentable.

    Sometimes, there needs to be more flexibility because of a lack of understanding.

    However, other times you can put in a little effort and get the understanding needed for a good data model that simplifies future velocity and correctness a ton.

    • temporarely 8 days ago

      I think you miss the fine point here. Ontological truth =/= Good domain modeling. OP is not arguing against domain modeling, rather a likely common trap of creating an analog of 'the world' (however delimited) as foundation for the model. I know exactly what they mean because I am guilty as charged. I used to do this all the time in mid career.

      • tilt_error 8 days ago

        Yes, you shouldn't "boil the ocean".

        • kybernetikos 7 days ago

          You should usually also not "model the ocean"

  • crabmusket 8 days ago

    This is very important. You can't build software given only knowledge of a domain. You also need requirements.

    The classic OOP educational example of `class Dog extends Pet` is a case in point. In what sense is a dog like a pet? The only way to know the answer to that is to look at it through the lens of our requirements. Are we building a vet appointment booking app, or a video game?

    Educational examples can be misleading due to lacking real requirements, and lead programmers to reproduce oversimplified structures.

    • crabmusket 8 days ago

      I wrote a more fleshed-out example of what I'm talking about on Reddit[1], but to save you the click through I'll reproduce it here:

      [begin quote]

      > [OOP] also make you have to think heavly about design, as you'll get yourself thinking about stupid things like "Does a buyer sell the goods, or does the customer buy the goods? What if we’re firing someone? Does a manager fire the person? Does the person fire themself? What if HR initiated the process, not the manager?"

      This smells like very poor application of OOP. Let's take the firing example, it sounds like you're trying to design code like this:

        employee1.fire(employee2);
      
      But that's almost never what you want to do. Instead of designing classes that map to "things" like employees, it's likely your classes should be modelling business use cases like EmployeeTerminationProcess. To make up a bunch of stuff,

        let process = EmployeeTerminationProcess.create({
          initiator: employee1,
          toBeFired: employee2,
        }).begin();
      
        // later,
        if (process.hasEnoughDocumentationBeenProvided()) {
          mailer.send(process.makeTerminationEmail());
        }
        // or maybe,
        if (process.isComplete()) {
          process.archiveAllMaterials();
        }
      
      `create` might even be a factory which returns instances of different specialised classes for different processes. Say if the documentation requirements are different when the process is initiated by a manager or by HR.

      Trying to fit the colloquial English phrase "Bob was fired by Janet" directly into a line of code results in weirdness; instead of modelling English, you should be modelling the business process itself.

      The problems you have with classes are common, and a result of crappy education and crappy existing codebases. It seems like industry and academia have gone through a wave of "let's model tangible objects as classes" which was never a good idea.

      With any luck, the rise of functional programming will remind practitioners in OOP languages that abstractions are real (as David Deutsch described it) and that every tutorial containing class Dog extends Animal needs to be put in a bin.

      [end quote]

      [1]: https://www.reddit.com/r/node/comments/pyokmi/a_reflection_a...

      • stickfigure 6 days ago

        That kind of thing is hideous and should be adopted only when the system is showing stress from the simpler, direct function-that-does-something approach.

        On the other hand, fixing the messes left over by people with a penchant for overengineering keeps me rich. So have at it.

  • epgui 8 days ago

    You make it sound like understanding the domain is some sort of pontification…

  • campbel 8 days ago

    Treating features as independent mutations may incrementally improve the software, but this approach can quickly lead to an incoherent codebase that is difficult to modify without introducing regressions. Proper domain modeling is done so we can organize around patterns.

  • drekipus 8 days ago

    This is something that I'm trying to take note of. Thanks for this comment.

BurningFrog 8 days ago

That's funny.

"Maintain one source of truth" is exactly what I mean by "DRY".

The question I ask myself constantly is "if this had to change, in how many places would I have to change it?". If the number is not 1, I consider it a "bug in waiting".

This is fairly simple for data. For code it can be more complex, but it's really about that same question. Is some algorithm changes, will I have to change code in 2 places?

I've seen people mistakenly bundle together similar code in the name of DRY like OP says, and it's an unfortunate thing. I recommend asking "the question" if you're unsure!

  • nucleardog 8 days ago

    The way I tie these together in phrasing it to teams is to ask yourself “Is this _necessarily_ the same or _coincidentally_ the same?”.

    Code which is necessarily the same might be something like “how to calculate an invoice total”. If you need to calculate totals in multiple places, you can’t calculate different totals for the same invoice. Have a single source of truth for this.

    Code which is coincidentally the same might be something like “well, to get an invoice total we add up all these prices multiplied by quantity… and this spreadsheet we’re importing has the product cost and a multiplier to reach a desired profit, so that’s the same dollar amount multiplied by a number again!”. Creating a single implementation for this will only cause pain.

    Even if these things are the same _right now_, there’s nothing saying they must remain the same. And as they diverge, you are going to be either creating brittle franken-code that causes bugs in seemingly-unrelated parts of the application when you make (slow, tedious) changes, or you’re going to keep only the repeated logic as it evolves and you will make the code more and more generic until it provides no useful abstraction whatsoever.

    Don’t repeat things that must be the same. Do repeat things that just happen to be the same _right now_.

    • 7bit 8 days ago

      > The way I tie these together in phrasing it to teams is to ask yourself “Is this _necessarily_ the same or _coincidentally_ the same?”.

      I'm a hobbyist. One of my apps recently had the exact same five lines of code in two functions.

      After some thinking I decided to leave it like that. Moving it into a third function seemed just additional work without no benefit.

      My question was: if I had to change A, would I have to change B? The answer was: Perhaps, but not in all cases. And if I had to, the change would have me look at both functions anyway, so it is not that I could just forget to change B. And it would be unlikely it changes at all in the next 2 years.

  • temporarely 8 days ago

    No, one source of truth of 'the state'. A "system of record" in db parlance. DRY is concerned with the code, i.e. algorithm.

  • eddd-ddde 8 days ago

    Exactly. That 2 functions are doing something similar _does not_ mean they should be abstracted into one thing.

    I rather copy paste my sql query and maintain 2 almost identical copies unless I am convinced they are actually a single query with some parameter.

  • xmprt 8 days ago

    I like the system you have in place for it. I feel like I've been doing the same thing subconsciously without realizing how to verbalize it when others make that kind of mistake in code reviews. There's a difference between two methods that look similar and therefore should "reuse" code versus two methods that are referring to the same requirement and where if anything about that requirement changes then both methods should change even if the methods themselves look nothing alike.

foobarkey 8 days ago

+1000 to PRY, if something needs to possibly evolve separately dont force it into some shared abstraction, I think DRY probably causes the most bad code but we still keep doing it.

Agree with mocks also, currently not even using them, just go with integration tests and set db back to known state between every test run, oh and wiremock for rest

  • srid 8 days ago

    Related to PRY, see "rule of three" - which I think is a reasonable position between DRY and PRY.

    https://en.wikipedia.org/wiki/Rule_of_three_(computer_progra...

    Rule of three ("Three strikes and you refactor") is a code refactoring rule of thumb to decide when similar pieces of code should be refactored to avoid duplication. It states that two instances of similar code do not require refactoring, but when similar code is used three times, it should be extracted into a new procedure.

  • coffeebeqn 8 days ago

    Bad abstractions have a heavy cost on readability and just your whole teams ability to reason about the program.

    If your “DRY” change PR removes 1 repeated piece of code but adds in 1 kind of nonsensical abstraction, 1 extra coupling between two pretty unrelated things that use that abstraction, 1 change to the input and output of the function, 1 test suite testing for two separate “things”, etc. it’s not sounding like such a clear positive contribution to the codebase.

    If we want to go by some of the old adages then a non-dogmatic reading of Single Responsibility - the abstraction should be about one thing - is pretty good in my book.

    • 7bit 8 days ago

      Django class based views come to mind. I feel the DRY principle made them an absolute mess if you a but more flexibility that what they bring by default.

      Or I'm just dumb.

  • margorczynski 8 days ago

    Yep, one of the basics I do now is a Docker compose with the DB, additionally it tests the migrations.

    There's little to no point in doing mocks if your logic is not really complicated where you would need to separate testing it from the integration test.

    I think a lot of people took "TDD" a bit too much to the heart and it ended up maligned where always more tests == better.

    • coffeebeqn 8 days ago

      People forget that software engineering is engineering sure, but also art and operations. Anything too dogmatic is going to have negatives and it’s kind of obnoxious when someone just quotes their “bible” when you’re trying to have an honest conversation.

      I know the book said so, but can we please just talk about the reality of our situation.

  • osigurdson 8 days ago

    One thing that annoys me to no end in our industry is lazy, unexamined phrase based development.

apwell23 8 days ago

I feel like 2010 was peak of software engineering. Rails, clojure, software craftsmanship, CI/CD, TDD, Design patterns, martin fowler. I was proud of being a software engineer.

Now it feels like mostly gluing together cloud apis and fighting over which cloud product to choose and Managers who don't give a F about software engineering.

  • BossingAround 7 days ago

    Managers have never cared about software engineering.

Waterluvian 8 days ago

> Far too many times I’ve seen code that looks mostly the same try to get abstracted out into a “re-usable” class.

I’ve had a hard time explaining this but I know it when I see it. Two things that are the same shape but aren’t really semantically related. So if you try to share code, your abstraction breaks the moment they start to inevitably express how they’re not related.

  • jandrewrogers 8 days ago

    These cases are almost always an example of convergent evolution as in biology. Common environmental constraints drive them toward the same shape and behavior but their DNA is fundamentally different. As soon as the constraints weaken or change, they diverge again to reflect their underlying DNA.

    • Waterluvian 8 days ago

      Oh no. Are we talking diamond-shaped inheritance… in real life?!

    • drewcoo 8 days ago

      If it walks like a crab . . .

  • Maxatar 8 days ago

    Sure, if the abstraction is very broad like a class, but I find I have a lot of success factoring out code that mostly looks the same into a plain function.

    • piva00 8 days ago

      Sure, and as usual nuance needs to be applied. I've seen these refactorings also break at some point over time, after the function needs to be expanded and delegate to other functions when extended, ending up in a 3-5 layer of function delegation that just makes me angry to read because it's stupid cognitive load to follow a dead simple code path.

      It's never that cut and dry, there's always nuance to almost every pattern we try to apply in code.

      • Maxatar 8 days ago

        That sounds like a different problem. There is a difference between taking duplicated code and eliminating that duplication by using a function, and having a short function that calls another tiny function that in turn calls yet another tiny function when all of these tiny functions can just be merged together...

        The problem you're mentioning can happen regardless of whether someone repeats the same thing everywhere, or whether someone factors out common code into a single function.

        John Carmack wrote an article [1] about the point you bring up, and how humans can understand a certain degree of complexity more than they can understand a very tiny amount of complexity or too much complexity. There is a sweet spot when it comes to function call length, not too long, not too short.

        But once again this is a different matter altogether.

        [1] http://number-none.com/blow/blog/programming/2014/09/26/carm...

        • piva00 8 days ago

          That's exactly what I meant by nuance, because the refactor into a function could never predict that 6 months down the line that specific function needs to be expanded, and someone might decide to delegate the call to another place. And the mess starts from trying to not repeat some code.

          Sometimes I avoided extracting a function out of some duplicated code because I intuitively saw that they had some potential divergence down the line, and it has paid off quite a few times. That's part of the problem as well, it depends on wisdom and intuition only gained through experience.

          • Maxatar 8 days ago

            So expand upon it, functions trivially compose unlike classes. There is nothing about deduplicating a function that prevents you from diverging from it later on.

            There could be some judgement calls and technical expertise needed to determine the right amount of parameterization of a function, but I'm not sure I follow the idea that a simple function, which is nothing more than a "name(parameters...)" that needs to be changed 6 months down the line is somehow causing a giant mess.

            If anything, the benefit of deduplicating is that if you find there is a bug in your implementation, or an optimization, you can fix the bug or apply the optimization in one place and get the benefit at multiple call sites, as opposed to having to go to a bunch of places.

            With that said, it's not my position to tell you not to repeat yourself. If you find repeating code helps you for whatever reason, go for it. I'm just saying that it's perfectly fine for developers to avoid repeating themselves by using functions and their code won't become a giant mess as you seem to indicate it will. Functions are very simple, flexible, and elegant abstractions for code reuse and even if you somehow found that you had to eliminate a function and repeat it in multiple places, that's a trivial operation to perform that most IDEs can do, so it's not like you're locked into this.

            The same can not be said if you use a class as a way to avoid repetition.

      • Waterluvian 8 days ago

        The way I’ve seen this failure expressed is when a handful of helper functions gain a bunch of optional arguments to toggle behaviour that most of the callers don’t care about.

        • Maxatar 8 days ago

          Sure, people can write bad functions, I'll never disagree with that. I tend not avoid allowing what bad programmers do dictate how I or people on my team write code.

yatz 8 days ago

Plus: 1. Avoid circular dependencies at any cost. 2. Ensure functions and classes do one thing and one thing only (SRP). 3. Cap functions at 50 lines and a class at 500. 4. Group managers, services, views, models, and UI by features rather than class types.

mvdtnz 8 days ago

Absolutely agree with 1 ("Maintain one source of truth"), not just in a service but in an architecture. I am dealing with a microservice architecture where decisions have been made to store the same state across different services. Needless to say this is massively challenging to keep in sync and requires a ton of work keeping the production systems consistent. Not to mention the difficulty of setting up proper test environments where any given data store might be reset or refreshed at any given moment, causing all kinds of havoc and causing people to lose trust in the test environments.

For number 2, (please repeat yourself), the truth is in the middle. Don't repeat yourself when the proper abstractions present themselves, and always be on the lookout for a good abstraction. But don't force an abstraction where it's not appropriate. Where to draw the line is the hard part and in my opinion comes from experience and intuition. If in doubt, repeat yourself.

Number 3 (overused mocks) I find myself swimming against the tide of popular opinion. I think mocks are very valuable for unit testing. This doesn't mean you don't write your integration tests without the mocks, but I still think there's a ton of value in being able to quickly write a large number of fast unit tests that say "when this component does this, I expect the system under test to do that". As for leaking implementation details, I don't think it's such a big deal at the unit testing level. The details need to be tested somewhere. I appreciate this is an increasingly unpopular viewpoint but it has served me well.

For number 4 (minimize mutable state) this is software engineering 101, but always bears repeating.

  • rodgerd 8 days ago

    > Absolutely agree with 1 ("Maintain one source of truth"), not just in a service but in an architecture.

    Well, now you're tightly coupled against your single source of truth. That's a choice if you'd like your choices to be (a) more outages or (b) trying to build a perfectly available data store.

    It's ironic that the example is banking; banks have many sources of truth and eventual consistency as patterns and always have.

    • mvdtnz 8 days ago

      Either way you're coupled to your data. It has to come from somewhere. The alternative is that the "origin" of the data either posts it to me (so my origin has a direct dependency on me and any other service that wants to use that data) or the origin fires events which I subscribe to, in which case I have that same tight coupling but I need to deal with circumstances where the event source fails (no one likes to talk about this but it happens, often).

      Not only that but if I ever want to change data I now need to somehow inform the origin that the data has changed. Coupling me even more strongly.

      The coupling is inherent. Things are simpler if you keep the source of truth in one place.

grosales 8 days ago

DRY is about modularization and identifying higher level abstractions when possible (and useful). When you have a domain driven mindset, it's much easier to put in practice. But really, you need to learn how to balance things. And you can only get that natural intuition with practice, lots of practice. If you give up early and think repeating is ok all the time, then you will never gain that intuition.

Regarding mocks, there are mocks that can hurt you and mocks that will help you. Mocks that can hurt you are mocks you generate by hand and it represents an idea of what production is, or maybe the untested, specified contract. Mocks that help you are the ones that are automatically generated representing actually what production has. Mocks like these have saved me from potential P1s many times - and millions in business losses. In an ideal world, I wouldn't need mocks, but also in an ideal world LLMs would do my testing.

I joined HN back in 2008 and I am mostly a lurker, but when I see an article that just plainly promotes bad practices with no samples of code or without going in depth. An article that would have been voted down or ignored to oblivion back in the early days of HN. I have to say something. I have seen enough bad code (specially in recent years) - I am afraid the new generation is getting their tips from all the wrong places.

  • drewcoo 8 days ago

    > in an ideal world LLMs would do my testing

    I feel exactly the opposite. In an ideal world, I would write tests to define behaviors and LLMs would write and optimize the production code.

  • epgui 7 days ago

    The kind of application of DRY you describe is not what the author has a problem with. What you describe is totally reasonable.

    Have you never seen more junior engineers make code 1000x worse (bordering on nonsensical), with them proudly saying they made it DRY-er? They are sometimes so focused on the one often-misapplied principle that they cannot reason about the produced code as a whole.

  • wonrax 8 days ago

    > Mocks that help you are the ones that are automatically generated representing actually what production has.

    I'm a novice when it comes to mocking, can you explain more on this or link some articles to read?

BadOakOx 8 days ago

1 and 2 kind of contradict themselves (or compliment, based on how much DRY we allow)... You want to make that abstraction when you need to have a single source of truth.

Also, the argument for 2 isn't very clear, it could be interpreted like the author doesn't understand inheritance.

  • jprete 8 days ago

    You should have one source of truth for data. But frequently multiple implementations of the same, or similar, interface don't actually have the same semantics. By merging them, you now have the same source of truth for two distinct concepts.

  • eddd-ddde 8 days ago

    They don't really contradict. That FOO and BAR look similar, does not mean they share some "truthfulness". Prematurely reaching to deduplicate and abstract them may lead to issues when you realise they aren't actually as similar as you thought, then you end up handling edge cases that wouldn't exist if you just kept the things separate to begin with.

    • AlphaSite 8 days ago

      The line I like to pull out is: things that look alike may not be alike.

monero-xmr 8 days ago

The hill I’ll die on is to avoid classes, everywhere. You don’t need classes. Just use functions and structs (or plain objects, etc.). OOP was a terrible mistake and mixing state with logic was a major catastrophe that poisoned a few generations of engineers. Burn the Gang of Four design patterns and scatter the ashes into the ocean.

  • wayeq 8 days ago

    Yikes. That kind of all-or-nothing thinking is a red flag.

    Structs and functions are great if you see yourself adding more new functions than data types in the future (the existing functions don't need to change!). OOP is great if you see yourself adding more data types (the existing classes don't need to change!).

  • breadwinner 8 days ago

    > OOP was a terrible mistake and mixing state with logic was a major catastrophe

    You're gonna have to justify that. Can't just take your word for it. Where do you store your state, if not in objects?

    • margorczynski 8 days ago

      The struct. OOP tries to combine the struct (data) and functionality (methods) into one and it is a bad idea.

      • breadwinner 8 days ago

        That's how the real world is. The state and the functionality are in one thing. OOP models the real world. Are you suggesting that data should be stored in global variables instead?

        • monero-xmr 8 days ago

          You don’t lose scoping by avoiding classes. I’m not really sure what you are asking. You store all data in primitives or structs, and pass it along to functions, which mutate the data. Much simpler to reason about, comprehend, debug, test, and so on.

          • breadwinner 8 days ago

            > You store all data in primitives or structs, and pass it along to functions

            OK, so in global variables, like in C language. And you think this is better than OOP. Glad that's working for you!

            • mrkeen 7 days ago

              > OK, so in global variables, like in C language.

              Not at all!

              This is the main reason OO has dominated for so long. OO has somehow taken on the meaning "Anything which isn't C".

              And it's not even an accurate representation of C, it's a straw man version of C. C programmers know the perils of global variables.

              Here is some code:

                int myState;
                void myFunc() {
              
                }  
              
              If I take the above code and make it look like C:

                #ifndef MYSTUFF_H
                #define MYSTUFF_H
                int myState;
                void myFunc() {
                  ...
                }
                #endif
              
              Yuck! Global variable! disgusting!

              Now I make it look like Java:

                class MyStuff {
                  int myState;
                  void myFunc() {
                    ...
                  }
                }
              
              This is somehow "encapsulated". But start calling myFunc() at different times from different threads and see if myState behaves more like a stack variable or a global variable.

              OO made it much more socially acceptable to pollute your code with this kind of "encapsulation". And OO devs decided that even this was too much of a straightjacket - some people liked C#'s take on properties, where you didn't have to work so hard writing getters and setters (further breaking encapsulation).

              Real C Programmers™ actually encapsulate:

                  my_state_t myFunc(my_state_t in) {..}
              • breadwinner 7 days ago

                In your last line of code the my_state_t is really the "this" pointer. You're simulating OOP.

                • mrkeen 7 days ago

                  Yes!

                  Once you take away inheritance ("prefer composition"), polymorphism (better offerings elsewhere), encapsulation (not encapsulation!), message-passing (better offerings elsewhere), you're left with a small syntactic trick that really wasn't that hard to do in C.

                  Even Lua gives you that [1]:

                  > This use of a self parameter is a central point in any object-oriented language. Most OO languages have this mechanism partly hidden from the programmer, so that she does not have to declare this parameter (although she still can use the name self or this inside a method). Lua can also hide this parameter, using the colon operator.

                      function Account.withdraw (self, v)
                        self.balance = self.balance - v
                      end
                  
                      function Account:withdraw (v)
                        self.balance = self.balance - v
                      end
                  
                  [1] https://www.lua.org/pil/16.html
                  • breadwinner 7 days ago

                    This is miniature OOP and that puts you in "major catastrophe" territory according to some on this thread.

            • curioussavage 8 days ago

              Uh no. You would typically retrieve the data in a function and assign it to a variable scoped to the function. The struct or array then gets passed through a pipeline of functions to do something possibly writing the data back to disk or the db.

              Even if there is some data I want to keep in memory there is no need for a global usually. In go I may just keep it in a var in a goroutine that doesn’t exit until the process does.

              • interlocutor 8 days ago

                In go you mix state with logic (example below), which apparently is a "major catastrophe" according to some on this thread.

                   type Engine struct {
                       HorsePower int
                   }
                
                   func (e Engine) Start() {
                       fmt.Println("Engine is starting with", e.HorsePower, "horsepower.")
                   }
        • throwawayqqq11 8 days ago

          > Are you suggesting that data should be stored in global variables instead?

          According to FP, data shouldnt be stored where possible and yes, if you can, store global or top level immutable blobs and pass by reference.

          > That's how the real world is.

          Well, maybe not. I dont rememer where it came to me that OOP is like classical physics, where everything has to get passed around with the speed of light and FP is like quantum physics, stateless until you do the computation. I dont know how accurate this analogy is but it still fascinates me since modelling reality is enlarge what programming is.

      • monero-xmr 8 days ago

        And that’s the essence of OOP. Once you grasp why that is such a colossal blunder, it’s obvious what a mistake the entire endeavor truly is.

  • foobarkey 8 days ago

    I will join you on that hill, just make a pureish function/method that does the thing its supposed to do and job done. Although in 10% of cases OOP and inheritance is useful

    • fuzztester 8 days ago

      OOP can be used without inheritance, using composition.

      Actually all four ways are possible: with either, neither, or both.

      The GoF book says to prefer composition over inheritance much of the time, in an early page of the book.

  • mvdtnz 8 days ago

    From 15 years industry experience my biggest takeaway is to ignore advice from anyone who claims their point of view applies "everywhere".

    • 000ooo000 8 days ago

      Amen. Everything in software is about tradeoffs. The only sensible answers to software questions start with "it depends". Dogmatic shittalking of OOP, a paradigm that quite a few developers are happily using to produce successful software, only highlights a lack of perspective and an unjustified arrogance.

    • monero-xmr 8 days ago

      Well I have over 20 years experience so maybe you need 5 more years or so

      • piva00 8 days ago

        Reaching 20 years mark here, and I kinda agree and disagree with your initial take.

        Hear me out, I don't think classes should be used to mutate state, or perform actions on their own. But they are a nice way to namespace and think about components properly. It can be achieved with just structs and functions (strongly typed, for the love of all deities) but giving a container like a class has its advantages to namespace some categories of functions belonging to their own structs.

        I agree that many other uses of classes (and fucking design patterns piled on top) were, and still are, a major source of pain in software development, I'm very glad to see that a lot of folks around the 15-25 years of experience realised the footguns are not worth it and adapted to write more readable "dumb" code.

        • eyelidlessness 8 days ago

          I share your take, and I’ll even say that classes are an underused tool when applying functional principles in mixed paradigm languages. If you use a class as a struct (with value semantics), you get:

          - A type

          - A name for it

          - A canonical way to build a value of that type

          - A consistent way to reference its semantic meaning at runtime and in any static/build time/documentation format

          - A singular, easily discoverable place to change it

          • tensor 8 days ago

            Most "non-oop" languages like Go and Rust have all of this. They also generally have interfaces. What they don't have is inheritance, which I think is what really differentiates OOP from structs with interfaces.

            • eyelidlessness 8 days ago

              Even inheritance of classes-as-structs is probably under-appreciated. It’s not quite as flexible as more general composition. But it’s not nearly as problematic as OOP inheritance, because it’s ultimately only a hierarchical composition of taxonomy and value construction semantics. It can become problematic if you apply the wrong taxonomy, but that would be true without inheritance as well.

        • sgarland 8 days ago

          > namespace

          Precisely. I’m a DBRE, not SWE, but I use Python a lot, and have written some internal tooling. Classes as namespacing is wonderful.

          • monero-xmr 8 days ago

            Just namespace with folder and file structure. Done

  • tensor 8 days ago

    Structs with functions and interfaces retains most of the good parts of OOP. I agree that inheritance seems to be more trouble than it's worth.

  • the_real_cher 8 days ago

    People create a bajillion non-deterministic little state machines in their code and act like it's no big deal.

  • klysm 8 days ago

    I generally agree with you. The only time I find classes make sense is when you need a stateful imperative handle. Those cases are rare.

    • quonn 8 days ago

      Correct, and even then it may be possible to just return some functions, capturing the state via closures.

      • klysm 8 days ago

        State via closures can be harder to think about in my opinion

klysm 8 days ago

I've been learning PRY over the years. I definitely used to be in the DRY mindset where I'd try to factor out any abstraction I could. The cost of abstractions wasn't clear to me at first. Now I approach abstractions with fear. I only take them on if breaking a pattern would cause problems.

realprimoh 8 days ago

Agree with not using mocks and following PRY especially. Oh man - it's so easy to want to make code so neat and clean with perfect reusable classes and tests, but honestly, its more effort than its worth in 99% of cases.

osigurdson 8 days ago

"Computers are very fast"

Better to know how much time things take.

  • crabmusket 8 days ago

    I did this for the first time recently and it was a super satisfying experience.

    I'm mostly a web dev, but I was experimenting with some very mathy Rust code. I did some back of the envelope calculations about how many cycles a CPU might take on various floating point operations, multiplied that by the number of items I was pumping through it, and arrived at a number that was 4x out... then realised the Rust compiler had performed auto-vectorisation with SSE2!

    I wasn't spot-on, but I was within like 20-30% I think. And that made me feel like a real engineer haha.

  • DavidPiper 7 days ago

    While I agree with you in isolation, the context for that line is:

    > but it’s important to figure out what is truly necessary storage-wise versus what can be derived on-the-fly. In the “v1” of something, I’ve found that minimizing as much mutable state as possible gets you pretty far.

    It seems like the author is specifically calling out that you shouldn't store derived data unless you either can't recreate it easily or have measured (usually a post-v1 activity) that it is faster to store/cache that derived data.

    • osigurdson 7 days ago

      I think the author is always creating a certain type of application where db -> web server -> web page latency and throughput don't cause any performance problems. My main point is, don't assume that all applications are the same. Know when you are in new territory and the old heuristics don't apply.

      I'm triggered by context free "I usually find that..." and "In my experience..." assertions.

hi-v-rocknroll 8 days ago

I don't follow and there isn't much content here.

DRY != god class / god methods. Strawman when a common problem is trivially-duplicated code sprawls and spews garbage and poorer maintainability over a codebase.