7 ms·
In general I agree that it's nice to make invalid states unrepresentable, but I'm not sure if I agree that this counts as a fundamental "invalid state". There i
by judofyr 6y ago
In general I agree that it's nice to make invalid states unrepresentable, but I'm not sure if I agree that this counts as a fundamental "invalid state". There is nothing about contracts which require that you can only have one active at the same time, or that that current one must be open ended.
From a practical point of view it might be advantageous if you maintain only a single contract with a customer at all times, but that is a business requirement which might be changed in the future.
I mention this mostly from experience: Multiple times I've designed systems where I've reduced the representable states to the minimum, and when some requirements change I realize I have to re-design the full system.
The new presented representation might make sense in this situation, but I'd be very wary of taking current business practices and make all other alternatives impossible to represent. It's a balancing act of course as you can go in the opposite direction and make it way too flexible.
> This poor choice was not just a theoretical problem - gaps in contracts were found on more than one occasion, requiring hours of engineering effort to hunt down and fix.
I'd like to hear more about what happened here. Was the problem that the default contract was not re-applied correctly? If so, changing the representation might not actually solve any problems — it make actually make it _worse_. A renewal of a contract typically involves some automated process where other services are involved (payment, invoicing, emails). The previous representation (with explicit start/end dates) made it possible for you to verify that everything was correct and lined up.
- ahoka 6y agoThen the code will be changed. Making up business requirements is the number one reason for instant legacy code. Code is not set in concrete, you can add that flexibility later when it is needed, but making everything overly generic to make it easier to "implement new requirements" only leads to code that is hard to change in my experience. Also don't forget that this is only an example.
- chii 6y agoThe YAGNI (you aint gonna need it) principle overrides the DRY principle imo.
- deleted 6y ago[deleted]
- chrisweekly 6y agoYes - as does AHA (Avoid Hasty Abstractions). IMHO.
- pc86 6y agoThis is partly why I've found it helpful to wait until there are at least 3 identical (not nearly identical) implementations of something before trying to make a more generic/abstracted version of it.
- dllthomas 6y agoI think these rules of thumb are... okay. But I think it's more helpful to go back to how DRY was initially defined ("Every piece of knowledge must have a single, unambiguous, authoritative representation within a system") and ask whether what I'm dealing with is actually "a piece of knowledge." There can be 10 identical copies, and if they just happen to be identical but they represent different things that might change independently, they should probably remain 10 identical, independent copies. Alternatively, if there are exactly two places something occurs in the code, but if they're out of sync the result is a broken system, you should think about unifying. DRY is too often treated as purely (or principally) syntactic, when that's actually much less useful.
- pc86 6y agoThis is a great way to look at it
- bluGill 6y agoComputers have been widespread for between 70 and 30 years (there is reason to debate, but whatever). Nearly everything has been done before: what you are doing isn't fundamentally new. There is lots of opportunity to add minor new features, reliability, or better user interfaces. But the fundamentals of what you are doing isn't new anymore. You can look at your past versions and what competitors do for guidance on what you will probably need next. If you have any broad knowledge of your problem domain you can make reasonable guesses as to what you will need and what you won't need. When replacing a subsystem I know if there will be 100 users of it in the future or if it is a leaf with 1 user - because I know what the old crufty subsystem has (the first case I spend days thinking about the interface, the second I design the interface when I integrate the one subsystem)
- deleted 6y ago[deleted]
- fennecfoxen 6y agoA few basic invariants about how your code is structured does a lot more than it being excessively "generic". Writing simple components that actually adhere to the single-responsibility principle (and composing them into more complex logic) doesn't just help out the mysterious future. It makes your existing codebase easier to understand and to validate up front.
- dkarl 6y agoThe requirements here aren't clear, but I'm guessing the requirement is to model the contracts the company actually has with customers. The business also tells you that there are never two contracts running at the same time. But are you actually going to believe that? Is this condition really "impossible?" A vital and necessary factor here is whether the system being designed has complete control of the creation of contracts. This is perhaps taken for granted by the author, but it's too important to leave implicit. You have three choices in a situation like this: make it impossible for contracts to overlap, model it, or don't model it and accept the consequences. Depending on the frequency and the consequences of the assumption being incorrect, maybe it's acceptable not to model it. Maybe not. My point is that you can't assume something is impossible unless you can actually prevent it from happening, and the author should not have sidestepped this part of the analysis (though possibly they meant for it to be understood that contract creation happens through this data model.) > Also don't forget that this is only an example. The problem is that this is an example meant to illustrate and justify a rule of thumb, but it's extremely, extremely simple. How often do you deal with requirements that are this simple, this mathematical? Is this really the kind of example you want to build a rule of thumb from? Realistically, when I hear requirements like this, I assume they're wrong (very common at the beginning of a project) and I get together with the product manager and ideally a domain expert representing the customer (if the product manager isn't too territorial about that being their job) and figure out what the hell the actual requirements are. What if a customer has a contract to rent 10 units of space at $5 and in the middle of that contract needs more 5 more units but the price has gone up $10? Do you tell them they have to cancel the existing contract at $5 and pay $10 for all their units if they want to add some? Or give them the new units at the old price? Or is it okay to represent the same customer by distinct customer records? I do like the principle of making invalid states unrepresentable, but I would like to add two supplementary principles: 1. Oftentimes what the business tells you about the data they produce is purely aspirational. "There will never be overlapping contracts," often means, "We swear we're going to stop creating overlapping contracts, and this time we really mean it." You have to follow up with questions like, "How often have we had overlapping contracts in the past? When was the most recent occurrence?" You should even ask, "When do we anticipate signing the next one?" A logically-oriented software developer might expect someone to take offense if you respond to "we don't sign contracts like that" with "Do you have any currently in the pipeline?" but this is a totally normal kind of question to ask. 2. When users give you a rule in their business requirements, they often take it for granted that the software will handle exceptions to the rule gracefully. They don't necessarily appreciate how bad things can go in software when something "impossible" happens. When they say, "Contracts will never overlap," you have to say, "What should happen when they do?" If you are talking to a mathematician or a programmer this might come off as questioning their competence, but most people will not find it unusual at all or at least will appreciate that the question is motivated by experience rather than disrespect. It's not like a math problem in school; it is legitimate to question the givens.
- steve_g 6y agoThere are trade-offs, of course, but I'm generally not a fan of using implicit defaults for business applications (i.e., the application infers the default when there's no data). If things go well, business data outlives business applications. After years or decades, it can be a major pain to figure out all the "secret" values that aren't actually in the data.
- jbergens 6y agoI think this depends on if you are talking about figuring out what contract a company had some time ago (say 18 months), or if you talk about the current default value. The last one should be very easy to find in the code and if you are migrating to a new system the information should be transferred in some way. For historical things you obviously has to store the data somewhere and then just figure out a nice way to do that. In this case it could just be a table of default contracts with some fields and a startdate. Then you can query for any point in the past and get the default contract that was active at that point.
- goto11 6y ago> Multiple times I've designed systems where I've reduced the representable states to the minimum, and when some requirements change I realize I have to re-design the full system. Yes, if requirements change, you change the design and code to support the new requirements. Compromising the consistency and maintainability of the current design to accommodate a hypothetical future requirement change is a bad trade-off IMHO, since you can't predict the future. A requirement change may happen in a completely different direction than the one you anticipated, and then you have the worst of both worlds. It is better to make code maintainable than making it flexible.
- tonyarkles 6y ago> since you can't predict the future That's one of those statements that makes sense, but is often not true. Very rarely does a client comes to me with a feature request that does require a pretty significant design change, but most of the time they're changes that were foreseen. Using this current article as an example, I love the way that they're storing the intervals to guarantee that they can't overlap. That's awesome! What I would likely end up doing, though, is use that as the underlying representation but still return individual interval objects through the query API with a start and end date on each interval. That way, if the "only one at a time" rule changes, the changes required are localized.
- philwelch 6y ago> Using this current article as an example, I love the way that they're storing the intervals to guarantee that they can't overlap. That's awesome! What I would likely end up doing, though, is use that as the underlying representation but still return individual interval objects through the query API with a start and end date on each interval. The article addresses that concept: > It is sometimes still useful to represent the periods as a sequence of start and end dates. It is trivial to project the set of dates in to this form. As long as the canonical representation is the set, the constraints will still hold.
- hondo77 6y ago> What I would likely end up doing, though, is use that as the underlying representation but still return individual interval objects through the query API with a start and end date on each interval. How the model you present to the user is represented in the database is an implementation detail.
- Zenst 6y ago> There is nothing about contracts which require that you can only have one active at the same time, or that that current one must be open ended. Agreed and yet for many systems, that flexability still eludes. One example I personally experienced was changing a phone contract. The contract had run for many years, so could be cancelled any time with one months notice. The new plan and contract was much better and yet a limitation played out doing this. Ended up that the systems at the telco was unable to activate the new contract until the old contract had ended. Whilst a new contract could be physically signed in a shop with a start date of the day of signing, and logged into the system. The provisioning backed was unable to activate it until the old contract had ceased as you can't have two contracts for the same phone number. That I do believe is a case of - whilst some things can run in parallel, others are locked to a single dependant resource. But every rule has an exception, it is with good design that you limit those exceptions impact.
- hackerfromthefu 6y agoI agree very much with your comment. I've found the fundamental principle that helps to keep the system extensible is to: make your system model the real world accurately. This involves building the system concepts closely mapped onto real world concepts without taking shortcuts. That way when the requirements change, all the fundamental pieces of the system stay valid, and only the piece that is changing tends to need updating. This helps to void the problem you mentioned of needing to re-design the full system, keeping the system extensible.
- stickfigure 6y agoI think the fundamental problem here is that the table/entity is incredibly badly named. What kind of contract has only three fields?! This isn't a case of YAGNI; no real-world "contract" is this simple. It appears to actually be some sort of contract_period or contract_duration, and probably has a link to a real "contract" object somewhere that contains the real meat of the concept. But it's hard to tell what's literal and what's the author trying to simplify the example for us.
- tlarkworthy 6y agoHard agree. The type system is no place for business logic. If you want to get fancy, maybe some database constraints you can change later. Business logic is constraints over the representation... not the representation itself. Also in generally the principle of "make invalid states unrepresentable” ends with the realization that only Idris can properly do it, which is not pragmatically useful.
- layoutIfNeeded 6y agoThis. Basically this is what event sourcing tries to solve: it lets you change your state representation to reflect new requirements, because you can always rebuild everything from the event log.
- bcrosby95 6y agoAbsent business requirements, I would love to see what you think fundamental invalid states for a contract would be. Every property of a contract I can come up with seems like a business requirement.
- arwhatever 6y agoI suspect there might exist a fundamental difference in the way automated-testing, static-type-leveraging software developers view software design and requirements compared to those who don't. I have no idea about the commenter here, but in my direct personal experience, the "those who don't" developers tend to regard software design and requirements to be toward the "write-only, then try to never change" end of the spectrum. I get a lot of "are you crazy? We can't change that!" type of pushback from the those-who-don'ts, regarding functional changes that should be reasonably manageable to my testing/typing mind.