[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)