[HN Gopher] The concept of class invariant in object-oriented pr...
___________________________________________________________________
The concept of class invariant in object-oriented programming
Author : PaulHoule
Score : 61 points
Date : 2023-01-03 15:35 UTC (7 hours ago)
(HTM) web link (arxiv.org)
(TXT) w3m dump (arxiv.org)
| anonymousDan wrote:
| Interesting, but a pity concurrency seems to cause the whole
| thing to break down.
| jstx1 wrote:
| > Class invariants -- consistency constraints preserved by ev-
| ery operation on objects of a given type -- are fundamental to
| building, understanding and verifying object-oriented programs.
|
| I think calling them "consistency constraints" would have made
| everything much easier to understand.
|
| Although I'll admit that I have a strong bias against nouns that
| can also be adjectives in programming terminology (invariant,
| generic, atomic, primitive etc). There's something about them
| that makes my brain spin an extra cycle to process them.
| okr wrote:
| Back in the day i struggled with the term invariant, same as
| with pre- and postconditions. Now i am actually quite happy
| being able to use them.
|
| Anyways,when i describe invariant i also say something is in
| itself consistent or true.
| hinkley wrote:
| Java came along with a free compiler and Meyer still insisted
| on charging for his. That's what killed Eiffel. And a doomed
| a generation and a half of devs to shitty object orientes
| analysis, culminating in Design Patterns, and reaching its
| absurd conclusion in Enterprise Design Patterns
| (institutionalized architectural astronautics). Which
| incidentally was published after Alexander had already moved
| on to new organizational principles.
| _a_a_a_ wrote:
| Quite so; it is _invariantly true_
| kazinator wrote:
| > [Callbacks] can find an object in a state not satisfying the
| invariant.
|
| Example: "please don't delete item X from the dictionary out of a
| traversal callback visiting item X." :)
| afterburner5 wrote:
| I worked for a company at some point where, due to the how the
| original product was built, testing before a release took between
| 3 and 5 days and was generally quite brittle.
|
| Within my first three months there, we had business requests to
| improve that. Invariants, and generally speaking, contracts for
| our models helped us out. Because we were able to run effective,
| cheap, fast tests, that proved our implementations were
| satisfying our requirements, pre and post conditions, invariants,
| the whole shebang, at the contract level, without even worrying
| about the actual underlying implementation.
|
| This greatly simplified the release tests, and it meant we were
| able to release much quicker, and with a higher confidence than
| before
| tabtab wrote:
| In business and administrative applications, there are very few
| reliable "invariants". Demo examples often use physics and
| geometry for a reason: Mother nature rarely changes those. Biz is
| different: new legislation, corporate edicts, new CEO's etc. can
| "rewrite" _anything_. These animal and shape demos gave too many
| the wrong impression of categories and types, creating
| "spaghetti class" messes that are still being cleaned up today.
|
| Has-A has replaced Is-A as the better modelling mechanism in my
| domain, but it turns out relational does Has-A better than OOP
| modelling because it's based around set theory, not category
| hierarchies.
| bmm6o wrote:
| If the requirements change then the code is changed and the
| invariants are updated as necessary.
| anonymousDan wrote:
| I don't see any problem with evolving invariants over time as
| business requirements change. The point is to be clear about
| your invariants for any given release.
| zabzonk wrote:
| i like the idea of invariants. i even like their implementation
| in eiffel. i even quite like eiffel as a language. too bad that
| Meyer never produced a useable implementation of it, and that he
| was so destroyed by envy over the success of C++.
| moritzwarhier wrote:
| Listing and implementing/asserting invariants of data structures
| feels underrated. To me, it makes so much more sense than trying
| to sell writing tests to my stakeholders.
| gavinray wrote:
| Class invariants are fundamental to Functional Programming too
|
| They are the core principle our of much-beloved Lexi Lambda's
| "Parse, Don't Validate" approach.
|
| One example might be a dataclass called
| "NonEmptyAlphanumericString" in Kotlin: data
| class NonEmptyAlphanumericString(val value: String) {
| init { require(value.isNotBlank()) { "String
| cannot be blank" } require(value.all {
| it.isLetterOrDigit() }) { "String must be alphanumeric" }
| } }
|
| Here we have a data type that is parsed from a basic "String" and
| enforces the invariants that the string is neither empty, nor
| contains non-alphanumeric characters.
|
| If it fails those invariants, we won't get a valid instance of
| "NonEmptyAlphanumericString" and "require()" will throw.
|
| (Though is this is a bit different because there is purposefully
| no mutable state here, if you enforce invariants again it's
| through creating a new instance of the immutable class)
| tinglymintyfrsh wrote:
| Also known as validations and business logic.
|
| It's interesting that in web paradigm dynamic languages and
| low-level systems languages, defensive programming assertions
| of invariants are widely dismissed. Validation and sanitizing
| of external input is often lacking and the source of many
| security issues.
|
| The problem domain is that most programming languages and
| application frameworks do a piss-poor job of separating and
| managing business logic around data storage and data mutation.
| MVC doesn't go nearly far enough. OTOH, there are fancy
| enterprisy business rule transaction systems that are divorced
| from most platforms.
|
| I think the key here is standardizing formal business rules in
| a portable "EBNF"/"markup" manner to be language and framework
| agnostic while not being laborious as COBOL-like Cucumber
| testing.
| clumsysmurf wrote:
| Thanks for this tip. I often see developers using data classes,
| with val properties, but not enforcing the invariants are
| correct with .copy().
| dfee wrote:
| I never got it, but on a reread:
|
| > Is it really parsing anything, or is it merely validating its
| input and returning a result?
|
| Ok. Now I guess I'm on board. Just feels like the word is
| wrong.
|
| > Consider: what is a parser? Really, a parser is just a
| function that consumes less-structured input and produces more-
| structured output. By its very nature, a parser is a partial
| function--some values in the domain do not correspond to any
| value in the range--so all parsers must have some notion of
| failure.
|
| Ok. It still feels like there's a better word. "Constrain"
| might sit better with me, though I've not spent enough time to
| really worry about its fit.
|
| https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
| ohwellhere wrote:
| Rather than "parsing", is it a form of type casting?
|
| To me it feels like downcasting in OOP from a parent class
| (more general) to a child class (more specific).
|
| In Swift there is the "conditional downcast" operator `as?`
| which does what the article discusses with the potentially
| failing conversion to `Maybe thing`: it returns the new more
| constrained as `.Some(thing)`, or returns `.None` if it is
| not the appropriate type.
|
| You can also go between non-hierarchical types with casting
| in most languages, like from ints to floats to strings, etc.
|
| Following from all of that and your language suggestion, I
| might call the practice a "constraining type cast".
|
| ---
|
| Although, all of that written, and I expect it's conversion
| rather than casting.
| dkersten wrote:
| > It still feels like there's a better word. "Constrain"
| might sit better with me, though I've not spent enough time
| to really worry about its fit.
|
| In clojure's "spec" library, it's called "conform". Confirm
| will validate and parse the input data based on the supplied
| spec and will either return a special "invalid" type or a
| structured value based on the structure the spec defined
| (which often pulls things out of the input data structure and
| returns this data as named fields in an output data structure
| -- basically it passes abr structures the input).
| lalaithion wrote:
| The key of the idea is you're not asserting that predicates
| on values are true, you're recognizing whether a value (of a
| type with few guarantees) conforms to the guarantees of a
| type (implicitly, a type with more guarantees), and
| transforming the value into a value of the output type.
|
| Parsing might not be the right word, but it's definitely an
| example of the above; we transform a string into a tree of
| tagged nodes.
| aatd86 wrote:
| Yes, I think another simpler way to do it is to have a set
| theoretic interpretation of types with subtyping.
|
| In which case, we can check that a variable can be type-
| asserted to a given subtype.
|
| A (sub)type being a set of propositions (i.e. defines a set
| of values).
| Snarwin wrote:
| Maybe "decode"? As in "decode a Unicode string", or "decode a
| JPEG image", or "decode an MP3 audio file."
| gavinray wrote:
| I didn't get it any of the times I read it either FWIW, it
| just randomly clicked one day for me while coding and I
| connected it back to the article
|
| I'm not the smartest tool in the shed, if I could dumb it
| down a little (sorry Alexis, and hopefully I'm doing you
| justice here!), I think it boils down to this:
|
| In many programs, you have logic like the below:
| function processOrderRequest(items: List<Item>, customer:
| Customer) { if (items.isEmpty()) { throw
| new IllegalArgumentException("Items cannot be empty")
| } if (customer.isBanned()) { throw new
| IllegalArgumentException("Customer is banned") }
| // ... orderAPI.placeOrder({ // .. final
| order object with some logic }) }
|
| This makes sense, but if you look at it another way, what
| you're trying to encode is some function like this:
| function processOrderRequest( items:
| ValidatedItemList, // A non-empty list of items we've know
| are okay to send customer: VettedCustomer // A
| customer we've vetted and know is okay to place an order
| ) -> PlacedOrderRequest { // ... }
|
| What this does is allow you to move the domain of validation
| away from the fragile business logic of your program, and
| into the deeply-embedded domain of your data types.
|
| It also allows you to control and refactor the validation
| logic much easier, since they live in your data-types and at
| boundary points
|
| Once I grokked this I was like, whoa. Neato idea.
| andrekandre wrote:
| would that be similar to a throwing init in swift?
| struct NonEmptyAlphanumericString { let value: String
| init(string: String) throws { guard !string.isEmpty
| else { throw Errors.emptyString }
| guard string.isAlphanumeric else { throw
| Errors.nonAlphanumericString } value =
| string } enum Errors: Error {
| case nonAlphanumericString case emptyString
| } }
| gavinray wrote:
| Seems identical to me =)
|
| Though yours also has the benefit of typed errors
|
| I've always liked Swift, but never written it. It seems like
| Kotlin and Swift are the most two similar languages.
| andrekandre wrote:
| yea i think its possible to express ideas from one to
| another in pretty straightforwards ways for sure
| torial wrote:
| There was a now-defunct python-like language built on .Net called
| Cobra that had class invariants and other contract functionality.
| It generally speaking had a lot of ideas focused on high quality
| programming built into the language which made it great to
| program against. I wish it had caught on....
|
| http://cobra-language.com/
| wheelerof4te wrote:
| Sorry, 99% of these buzzwords aren't applicable in real life,
| professional programming.
|
| Business just wants stuff to work, no matter how you make it
| work.
| rhdunn wrote:
| If your application has any reasonably complex data, validating
| that data when it is being added into the system (i.e. if it
| meets the invariants) prevents bugs later down the line. If the
| data is different to what the application expects, you need to
| spend time identifying that data, and correcting it. If the
| data is complex (e.g. book metadata) it can be expensive to
| correct.
|
| Having up-front validation of the input (e.g. checking book
| subject values) prevents a whole class of errors (such as
| spelling errors between country names), and enforces the
| contract between the data and application -- the list of things
| validated should be based on how the application works, such as
| required fields/values. If there is a new value that the
| business need, you then know you need to make an application
| change in order to support that value.
|
| Some values (like the list of countries) would be an open list,
| and would be best stored in the database/application as a list
| that can be updated outside of the code changes. Some other
| values (esp. those that need different code/logic to function)
| are closed lists that should be maintained and validated in the
| code (e.g. as enumeration values).
| UncleMeat wrote:
| In my experience, class invariants are among the very most
| useful techniques for enabling rapid development of features
| while managing mutable data. They allow you to "make stuff
| work" at a faster pace than otherwise.
| rmbyrro wrote:
| By "to work" you mean work while losing $50 million, like the
| stolen money mentioned in the article?
| fedeb95 wrote:
| Yes but also no. This kind of things, usually called "checks"
| in the wild, are nearly everywhere in code that works (i.e.
| wasn't fixed in a while and is triggered often)
| nh23423fefe wrote:
| its cool that "real life" "professional" and "business" are
| content free buzzwords. reads like an ignorant quine
| thesuperbigfrog wrote:
| >> Sorry, 99% of these buzzwords aren't applicable in real
| life, professional programming.
|
| Professionals use proven tools and techniques to get the job
| done. Design by Contract (DbC) is a proven technique that
| ensures code is robust and does what it is supposed to do.
|
| >> Business just wants stuff to work, no matter how you make it
| work.
|
| Business cares if shody code has security vulnerabilities that
| lets money or customer data be stolen. DbC has been proven to
| help prevent shody code.
| vidarh wrote:
| Various forms of checking invariants is applied in all kinds of
| "real life, professional programming" ranging from test suites
| to e.g. invariants checked by your database schema. Most code
| contains huge amounts of manually written invariants (every
| check to determine if a value is valid before e.g. setting an
| instance variable in a class is an attempt to maintain an
| invariant), and often it'd be a lot faster to get stuff to work
| with more comprehensive ability to enforce invariants - whether
| just during development or in production too.
| jrochkind1 wrote:
| In real life, most software is a fragile unreliable timebomb
| waiting to ruin businesses and lives.
| wpietri wrote:
| > Business just wants stuff to work, no matter how you make it
| work.
|
| For sure. But from there, it's our _professional
| responsibility_ to figure out good ways to make it work for as
| long as the business need is there.
|
| Imagine a doctor saying: "sorry, 99% of these buzzwords like
| 'endocrine system' aren't applicable; patients just want to
| feel good."
| zabzonk wrote:
| it wants it to work correctly, and it wants it to work fast.
| this is what all these "buzzwords" enable.
| eddsh1994 wrote:
| Class invariants are very useful in domain-driven development
| allowing you to have contracts between modules/services that
| can't break the underlying business rules. You would probably
| like them if you spent some time reading about them and compare
| it to your error/exception handling logic to deal with
| incorrect values.
___________________________________________________________________
(page generated 2023-01-03 23:01 UTC)