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