[HN Gopher] What If? Driven Development
       ___________________________________________________________________
        
       What If? Driven Development
        
       Author : ingve
       Score  : 46 points
       Date   : 2023-05-14 06:56 UTC (1 days ago)
        
 (HTM) web link (aftenposten.substack.com)
 (TXT) w3m dump (aftenposten.substack.com)
        
       | robertoandred wrote:
       | A hyphen, a hyphen, my kingdom for a hyphen.
        
       | ChrisMarshallNY wrote:
       | _> But to prevent the car breaking in future, you need to proceed
       | to 4 and 5, to discover that the car needed more regular
       | maintainence._
       | 
       | That's the theory behind highly mature CMMI levels. 5 means
       | ongoing process analysis.
       | 
       | That said, I hate CMMI (and I took the course, and drank the
       | Kool-Aid). It's hunting mice with a ten-gauge, for almost every
       | project, excepting really large, multidisciplinary ones. I have
       | worked on those (that were badly in need of process maturity).
       | 
       | Most projects that I've worked on; even fairly ambitious shipping
       | products, would have benefitted from improved process maturity,
       | but CMMI 4/5 or ISO9001 were way too burdensome.
       | 
       | Agile, I think, has tried to improve process, but it has been
       | turned into something that I don't think was originally
       | envisioned.
        
         | AstralStorm wrote:
         | Unless you happen to find yourself coding safety critical
         | software. Such as in actual medical industry where failures can
         | cost life.
        
           | ChrisMarshallNY wrote:
           | Yup. Good point.
           | 
           | I suspect that NASA is CMMI Level 6.
        
       | ouid wrote:
       | The example root cause is not even close to the "root cause",
       | this is just the root cause that auto-dealerships would like to
       | sell you. An additional why yieles something like:
       | 
       | 6) Regular maintenance is often more expensive than repairing the
       | car on failure, or buying a new car, and when maintenance
       | predicta that a part is going to fail, the solution is still to
       | replace it
        
         | taeric wrote:
         | I love when folks argue that I should replace my working
         | vehicle because the engine may go out and I would have to do a
         | several thousand dollar replacement. Which, yeah, would suck.
         | But how in the world did we start accepting the path to
         | financing a new vehicle as the answer?
        
         | moring wrote:
         | Also, there often isn't a single "root" cause. Causality is a
         | DAG. To start, the alternator belt can simply break even when
         | well-maintained. It can break when under stress due to another
         | part that isn't working correctly. The alternator belt could be
         | a well-known source of problems in this particular model, or
         | could have been replaced by a cheap one by a previous owner, or
         | could have been exposed to corrosive airborne chemicals due to
         | where the vehicle is typically parked.
         | 
         | Likewise, in the ID parsing example, there is a quick jump to
         | "It should ignore values past &, Because & is the field
         | separator" without asking why a field separator gets into this
         | parsing function at all. Is this function expected to handle
         | strings that contain such a separator? (All code operates on
         | certain assumptions, so it is futile to expect a parsing
         | function to handle "all" cases -- there simply isn't a useful
         | definition of "all" that could be the basis for that.)
        
       | eterps wrote:
       | Reminds me of: https://github.com/roc-
       | lang/roc/blob/main/FAQ.md#why-doesnt-...
        
         | awestroke wrote:
         | Very strange design. It completely ignores the potential
         | ergonomics of functions over Option<T> like map, unwrap_or,
         | flatten etc that you'd have to implement yourself for every
         | option-imitating enum.
        
           | mathgladiator wrote:
           | Yeah, I agree. My language has both maybe<T> and result<T>,
           | and maybe<T> is exceptionally convenient for all sorts of
           | things. As an example, the result of division is a
           | maybe<double> and I have all the typical operations defined
           | between maybe<double> and double so maybe<double> + double is
           | a maybe<double>. It feels nice to force people to think about
           | the edge conditions of why something isn't present.
        
         | nivertech wrote:
         | Error Handling in Elm
         | 
         | https://twitter.com/nivertech/status/1413392990211657729
         | 
         | It looks like Maybe/Option is just a Booleans-in-disguise. As
         | I'm in favor of banning Booleans (and if-statements), so it
         | makes sense to ban Maybe/Option-s also.
        
           | mrkeen wrote:
           | > It looks like Maybe/Option is just a Booleans-in-disguise.
           | 
           | This is a unique take. Care to expand?
        
             | 0x457 wrote:
             | Not the person you're asking, but I may have insight.
             | 
             | Realistically, it's no different, checking if something is
             | null or None. Yeah, some languages have a lot of sugar
             | around optional thanks to rich type systems.
             | 
             | Imagine the following pseudo rust code:
             | 
             | if let Some(x) = x {}
             | 
             | is not that different from
             | 
             | if x != null {}
             | 
             | Or any other type of boolean check.
             | 
             | I think you have to be around optional long enough to
             | understand the benefits.
        
               | mrkeen wrote:
               | > Realistically, it's no different, checking if something
               | is null or None.
               | 
               | That is correct. However, what languages with null lack
               | is a way to say "See this String here? This is actually a
               | String - not the absence of a String".
               | 
               | It is nonsense to encode both A and lack-of-A as A.
        
               | mathgladiator wrote:
               | But it is different because you can't dereference x
               | without the check. In other words, you can't really make
               | an accident.
               | 
               | My language has maybe<T>, and I have automatic "re-
               | wrapping" (not sure if there is a better term). where
               | 
               | For example, with                 record R { int x; }
               | maybe<R> r;
               | 
               | I can access r.x yet the type is maybe<int>.
        
           | awestroke wrote:
           | Wow, I hope I never have to work with you or anybody who
           | shares your opinions on this topic
        
           | dotancohen wrote:
           | > I'm in favor of banning Booleans (and if-statements)
           | 
           | I would love to hear your reasoning on this. I think that
           | either I'm misunderstanding you, or one of us is crazy ))
        
             | withinboredom wrote:
             | I've usually seen it expressed as:
             | 
             | > instead of accepting a boolean argument, write a new
             | function
             | 
             | Which can be expanded to
             | 
             | > instead of an if-statement, write a new function
             | 
             | You can't remove branching from software (otherwise, just
             | create a jpeg), but you can structure your software in such
             | a way that it appears not to branch.
             | 
             | I think it's weird, but I worked with someone like that and
             | learned a few interesting things (for example, I usually do
             | write a new function instead of adding an optional/boolean
             | argument -- unless it makes things worse for the caller
             | because the argument comes from outside the
             | context/domain). But with all due respect to people like
             | this, it's bananas to do it all the time.
        
       | [deleted]
        
       | kstenerud wrote:
       | > All this, without touching any tests. I didn't need to -
       | because the types provided the direction.
       | 
       | Which is all great and wonderful - until things change, and
       | requests can suddenly come in without an ID - meaning that you
       | must assign them an ID in your response. Not so easy to model
       | using only the type system!
       | 
       | And then later as session support is added, there are now two
       | IDs, but one of them must be omitted if the session is not
       | already active in the system. So now you must either make your
       | request aware of the session system so it can check, or you must
       | move the validation out of the request. Your type system is no
       | longer helping you avoid erroneous conditions, and your lack of
       | tests leaves you exposed.
       | 
       | It's all great when the system is small and easy to model...
        
         | williamdclt wrote:
         | Honestly, you're giving a really good scenario to show the
         | usefulness of a (good) type system. What you're describing can
         | be expressed very well in eg typescript, and while I'd write
         | test, I'd be confident writing the code only supported by tests
        
         | majormajor wrote:
         | Yeah, something like the history of Protobuf or Avro and all
         | the discussions about schema evolution in those things will
         | show how these systems break down in the real world. Schema
         | evolution and "supported types of requests" are hard in similar
         | ways.
         | 
         | Do that munging at the boundary? Your boundary is gonna
         | eventually be a heap of messy compatibility code to normalize
         | requests. PLUS you would need to build some sort of sidecar to
         | pass forward any necessary information about _which sort_ of
         | boundary request it was, once the business logic somewhere deep
         | in the system starts depending on more than just the post-
         | normalization form. If instead you want to reflect that in the
         | type system through the whole system you need some sort of
         | crazy-complex type hierarchy complete with a bunch of
         | conditional validation rules or lots of permutations of
         | types... or Maybes all over the place that just shift the
         | burden back to the business logic code, to know which ones are
         | REALLY needed when.
         | 
         | Cause your business logic has no limits on its complexity, but
         | your type system probably does.
        
         | turboponyy wrote:
         | Depends on the type system - sounds trivial in dependently
         | typed languages.
        
         | angarg12 wrote:
         | Here is a sharp take in the types vs tests debate
         | 
         | https://www.destroyallsoftware.com/talks/ideology
        
           | drewcoo wrote:
           | Now do one on attempting to make a simple point via
           | scannable/searchable text essays versus video-only content. I
           | take a hard stance on that debate.
        
         | mrkeen wrote:
         | > Which is all great and wonderful - until things change, and
         | requests can suddenly come in without an ID
         | 
         | But this is where type systems shine!
         | 
         | You create a RequestWithoutId and let the boundary accept it
         | (per your scenario). Hit compile and see what fails, in this
         | case, calling the next method into the system will fail,
         | because it expected a different type. Now you can choose to
         | keep plumbing RequestWithoutId through the system - guided only
         | by the type-checker, or you can write a
         | RequestWithoutId->RequestWithId implementation. If your system
         | functions correctly for-all RequestWithId, and you can make a
         | conversion for-all RequestWithoutIds, then your system will
         | function correctly for-all RequestWithoutIds.
         | 
         | But can you turn a RequestWithoutId into a RequestWithId?
         | Inspect the type of Id. If it's a UUID, it's most likely safe
         | to generate in-place, and you're gold. If it's a String or an
         | Int or an Any, then it's no longer mechanical and you probably
         | need to start thinking.
        
           | taeric wrote:
           | Where it starts to get more cumbersome than it is worth is
           | requests that can come in with any combination of N fields.
           | The generic path defined here would have RequestWithA,
           | RequestWithB, RequestWithC, etc. And, arguably, yes, that is
           | a good idea.
           | 
           | Realistically, nobody wants to review all of that. And you
           | will get phantom code of RequestWithZ that isn't actually
           | valid anymore, but nobody remembers it and there is nothing
           | about the type that makes that clear. In fact, you assume
           | that it has a type, it must be needed.
           | 
           | I suspect you will push for groups of the fields that are
           | relevant for the request. Such that it is RequestWithGroupA
           | and such, but, isn't that what "Request" is supposed to be
           | already? The group of data sent? So now you start down a
           | taxonomy of Requests and you build an impressive machine that
           | can deal with this.
           | 
           | Which is even more amusing, as that is called a program. And
           | often we accept some invalid possibilities for highly
           | theoretical reasons that help keep the programs easy to read
           | for the people using them.
        
             | mrkeen wrote:
             | > And you will get phantom code of RequestWithZ that isn't
             | actually valid anymore
             | 
             | It is the opposite. My IDE will show "no usages of
             | RequestWithZ". I will not get any such help if it was
             | encoded as Request.get("z").
        
               | majormajor wrote:
               | Nah, your IDE will show all the usages of RequestWithZ,
               | but won't tell you anything about how the thing that sent
               | RequestWithZ has been gone for two years. You'd still
               | need instrumentation with that.
        
               | taeric wrote:
               | As the sibling post said, you probably have a ton of code
               | that deals with what happens when a RequestWithZ appears.
               | The thing that changed is that that isn't a thing that
               | can actually be sent to you.
               | 
               | Now, you are right that you can contrive a situation
               | where you can choke off all parts that trim this down.
               | And then you can use the type system as a tool to help
               | delete dead code. When it works, it is a great tool. Just
               | like the example in the original article. Please don't
               | take my point as a complete teardown of the idea. Just
               | don't oversell it, either.
        
           | hbrn wrote:
           | They seem to shine, but it's a mirage.
           | 
           | Once you start naming your types "RequestWithoutId" you have
           | committed to using language alien to everyone but engineers
           | (might even be alien to engineers).
           | 
           | There are tremendous benefits in using "ubiquitous language"
           | (in DDD terms), and type-obsession means you'll never get
           | there. Business and engineering goals will never converge.
           | Engineers will live in their own little world,
           | procrastinating in their type puzzles and feeling productive
           | doing so.
        
             | kuratkull wrote:
             | Those kinds of systems are a real joy if designed
             | correctly. You can usually pretty safely assume that the
             | object does not mutate (much), and you can very easily find
             | out what you need to do to pass that object forward into a
             | function that requires RequestWithId.
             | 
             | Look up the concept of "make illegal states
             | unrepresentable."
             | 
             | EDIT: RequestWithoutId is not the best example of this, but
             | the point remains
        
             | jkaptur wrote:
             | Clear naming is important, and types _help_ with that.
             | "RequestWithoutId" is clunky, but it is easy to explain to
             | an (interested!) business-side person that you have
             | "external requests" (from the outside world) that you
             | validate and transform to "internal requests" (passed
             | around your system). It's certainly easier than if you have
             | no names or types at all!
        
             | Terr_ wrote:
             | That's not a problem with the approach, that's a problem
             | with the specific naming.
             | 
             | An original request without an ID still has some kind of
             | business relationship, even if only because it really did
             | exist at one point in the past. So there's _some_ proper
             | name out there, even if it's something like InternalRequest
             | or InformalRequest or ObsoleteRequest or
             | BeforeTheMergerRequest.
        
       | pphysch wrote:
       | Correctness advocates like the author tend to take the concept of
       | "correct" for granted.
       | 
       | 99% of software projects don't have a reliable definition of
       | "correct". They don't have a watertight ontology that you can
       | infer types from. Requirements continuously change. Your "Whys"
       | quickly end with "because so-and-so said so", and will change
       | tomorrow. "What if?" Driven Development presumes that there is a
       | finite and known set of states that you can design around.
       | 
       | Most software development (outside of extremely well-studied
       | domains, like financial exchanges and aerospace) occurs in the
       | absence of well-defined specifications. That's the fundamental
       | reason why correctness-oriented paradigms and languages aren't
       | popular and never will be. And that's okay.
        
         | crocsocks wrote:
         | Yep, tough to type business logic.
        
         | hbrn wrote:
         | Certain teams do suffer from incorrectness _they_ produce, and
         | I think obsession with correctness can be beneficial for them.
         | But it 's a painkiller, not a cure.
         | 
         | Once you build a good team, the source of incorrectness shifts
         | to the vagueness of the requirements and domain complexity. And
         | indeed, attempting to _solve_ it (be it with types or with
         | tests) is pointless.
         | 
         | In essence, if your team finds a lot of benefits in types, you
         | have a mediocre team. And mediocrity is almost unavoidable at
         | scale, that's why big companies heavily invest into those
         | correctness procedures (unit tests, types, various forms of
         | bureaucracy, etc).
        
         | goostavos wrote:
         | I'll give a slightly different take. I work on large financial
         | systems at $MegaCorp. Even though requirements change all the
         | time (often because "the government _said so_ "), are ephemeral
         | at best, and frequently contradictory due crossing entire
         | teams, orgs, and charters.... Stealing every possible tool that
         | can move the needle towards "correctness" has been crazy
         | valuable in my experience.
         | 
         | I've gotten on board the TLA+ train. Just the act of formally
         | specifying what you do know makes everything you don't
         | painfully obvious. Formal models also make dealing with that
         | constant change easier, because The Rules live in one concrete
         | place, in a formal modeling language, and free from the
         | ambiguities that requirements expressed in English prose are
         | subject to. It's not a perfect solution, but it can help cut
         | through the noise.
        
           | pphysch wrote:
           | More power to you in specifying your processes. A potential
           | issue here is that your bosses and other stakeholders aren't
           | part of the specification meta-process. Can you talk TLA+
           | with your boss? What about your boss's boss? Do they even
           | care?
           | 
           | A bad specification is worse than no specification; this was
           | one of the lessons of OOP-mania (a rigid ontology works
           | great, until it doesn't, and then it's a disaster). An
           | ostensibly correct "specification" inferred by a lone
           | engineer could become Wrong tomorrow after stakeholder input.
           | 
           | That's why I would recommend against these sort of
           | correctness-oriented paradigms _unless_ there is widespread
           | buy-in and understanding from your stakeholders, company
           | culture, etc.
           | 
           | Of course there is merit to using tools like TLA+ to model
           | distributed systems and assist development, but that
           | shouldn't be conflated with having a well-defined
           | specification from the get-go.
        
       | d--b wrote:
       | Maybe the author doesn't realize this but "what-if-driven
       | development" is one of the worst programming advice one can give.
       | 
       | The problem is that it gets interpreted as "I've been asked xxx
       | but what if what they really want is yyy". And this leads to
       | missed requirements, premature optimization, and all other evils.
        
         | kuratkull wrote:
         | "I've been asked xxx but what if what they really want is yyy"
         | - in my experience they usually want yyy, xxx is just how they
         | managed to write it down using existing
         | terminology/environment.
        
       ___________________________________________________________________
       (page generated 2023-05-15 23:02 UTC)