Open/Closed and Expected Outcomes

When I’m trying to be a good developer and apply SOLID principles to my coding practice, I usually get a little frustrated with applying something so abstract.  One issue that has frustrated me a lot is with the Open/Closed principle: when to say that a piece of code/entity is now closed to change.

After many years of frustration and yelling “Come on!” to my monitor, my thinking about this principle has changed:

An entity should be closed to changing already established expected outcomes.

If some other piece of code or system is using your code that you wanna change, you cannot change anything that the calling piece of code/system is expecting.

A very simple example is a webapi: if a webapi has a “name” property and some other client is using your webapi and is expecting there to be a “name” property in the return data, you can’t change the “name” property to “title”: you’re breaking expectations.  All hell would break loose!

Limit expectations of the code/software entity as much as possible.

So what helps me be a more proactive lazy developer is to try to limit expectations of the code/software entity as much as possible.  This has not only helped me with creating easier maintainable and extensible code, but it has also helped me with testing.

Breaking Tests

With this view of Open/Closed, I now have a different view of mocking dependencies: they add more expectations!

I’ve been doing way more integration testing than unit testing.  Ya, I know, but hear me out.  The big reason I’ve been focused more on integration testing is to limit this issue of tests adding more expectations to my code entities.

Webapi’s are the easiest thing to create integration tests for: create a seed database, have the test runner spin up the webapi service, and the tests make requests to the webapi’s.  Just assert expectations of the results of the request.  I have little mocking involved, only for mocking communication to other systems. This allows us to “change” webapis easily.  What I mean by “change,” is the ability to add additional functionality without worry of breaking old tests.

If we need to change expectations of a webapi route, we simple create a new version of it.  I usually have v1,v2, etc. in the base of the route, but I digress.

Lately we’ve been creating more asynchronous job workflows, which require a lot of processing and file I/O.  Full integration tests for these would take forever, so we need to do more unit testing for these.  This adds more expectations to the code which makes our unit tests more brittle: if I add another piece to the workflow later, all my other tests break because they didn’t know about any added dependencies.  Not I have to go back and add mock dependencies to old tests, which should never happen.

Right now my answer for this is to create a new version of the workflow and start adding new tests to it, along with duplicating old tests for the original version.

Extend or replace code/software entities if about to break expectations, even for mock dependencies.

The benefit of a new version is that I’m not getting confused if the broken tests are tell me that expectations have been broken or the tests itself is broken.

Overview

Limiting expectations of a software entity will help you be able to easily extend it later. Mock dependencies within tests create additional expectations for your code so try to do more integration tests if possible to help eliminate unneeded expectations.

Leave a comment