Getting data out of your system

We have data. It needs sharing.  What are the options?

One must assume that the data is required, so sharing it is necessary.

We could share the database: I’ve written about burning my fingers on doing this as well as the drawbacks of doing it if you’re forced to.  So what are other options?

If we do not provide SQL as our API, then we need to build something to provide that.

We could build an HTTP API to be called.  This API could provide the state of the data now, or we could attempt to provide a solution that provides a flow of events.

We could write data to another database and that is the contract that we share.  This still provides SQL as the API but at least the main application database can change at will as long as the contract into the secondary database is maintained and supported.

We could event the data and another service could listen to the events, reads the data and makes it available in another database and that is the contract with the consumer(s).

It is inevitable that something needs to write the data somewhere for the other application that wants it to gain access to it.  It will depend on what that application is as to what makes the most sense. But something will need to be built to provide the data.

There are many solutions that will hopefully keep your system changeable.  Find one that does not require large amounts of up front work, continuous maintenance, and provides the feedback needed to tells us if we are breaking our consumers.

Advertisements

What are the drawbacks of database integration?

The simplicity of integrating at the database is very appealing.  I’ve been draw into the trap of being caught out and getting my fingers burnt in the past.  So what are the real drawbacks?

TL;DR

  • Why isn’t this the norm for all 3rd party integrations? Those reasons all apply.
  • Avoid writing from multiple applications
  • Experimenting to learn is different to maintaining something long term.
  • Be aware that sharing the entire database is exposing the entire database as the public API.
  • Not knowing what you can change freely introduces friction.Reducing friction is a Good Idea.
  • Provide feedback to know when something has changed that should not have. Contract style tests may help.
  • Take steps to limit the exposed API/contract. Agreeing to share only specific tables may help. Maybe only share tables with a certain prefix.

Why isn’t this the norm for all 3rd party integrations?

Start with thinking about why we generally expose APIs instead of providing SQL database access directly.  All of those reasons will be present whether providing the integration point to a 3rd party company or to any other team – internal or external.

Below are some thoughts on what could be some of those problems.

Avoid writing from multiple applications

There are many patterns that try to help avoid updates from disparate places.  OO discourages direct updates through encapsulation – you cannot touch my data except through my interface.  In DDD you can use the aggregate pattern in order to help reason about changes to data which must all go through the Aggregate Root. The functional solution is to make everything immutable.  When using a pattern like Master Data – we’re trying to solve this problem by making an application / service the single source of truth.  All of these mechanisms are intended to help us reason about software. Software that writes to the database from multiple different codebases can be very hard to reason about.

Writing data from multiple applications violates the Don’t Repeat Yourself principle.  Knowledge about the tables, their defaults and their validations all need to be shared across multiple applications.  It is also hard for a developer to discover that a second application is writing to the table.  Doing a search in the repo they are working in will not find the access.  This can result in applications writing different defaults, validations, data types or not being aware of new columns or the meaning of new data.

A mitigation to this is to build a gem to share.  This is great until we do not keep all the applications on the same gem version and therefore we still have the same problems.  We need to lock step deploy every application using the gem to keep them in sync.  We now are building a distributed monolith.

What about read only – surely that is fine?

Maybe.

If you’re doing it to experiment and learn if something (like a 3rd party tool) is valuable, then definitely learn with direct integration.  After we’ve learnt, consider what the long term implementation is.  The pain that grows with this solution is in the long term maintenance and changeability of the solution, not the short term win.

For a long term solution, if you never change the database schema at all, then this is the quickest, easiest and – if you’re 100% right – most painless mechanism.  If you’re wrong, you’re probably in for pain.  How much pain depends on how rapidly things need to change and potentially how much the consumer is actually interested in.

The biggest problem is that we don’t know what will happen in the future, so assuming that we will never change the database in the future may always be a naïve choice.

What’s the big deal?

When you share your database you share your entire schema to be coupled to.  This is like exposing all of your data via an API where the API is every single SQL query that can be made.  Any change that is made to the schema can impact the consumer. If you give the entire schema that means you may need to ask permission for any change to your database schema as you do not know if the change you are making will impact the consumer.

The database schema is the API contract

Any shared API is a shared contract that the team owning the API guarantees to keep for all consumers.  When changing the API there needs to be negotiation and planning.  When we provide our database to another team to consume, the entire database becomes the contract that we guarantee to maintain. And any changes to that contract must be negotiated.

Why is that bad?

If we need to ask permission to change something it will introduce friction.  We need to remember to talk to another team. We then need to actually talk to the other team.  Other teams have their own priorities.  Given the best will in the world, communication still takes non-zero time.  And it will need to happen for every single change we make.  This slows us down, particularly when we’re making a lot of changes.

Friction slows us down.  Can we solve that?

The friction around needing to ask permission can introduce several responses

  • We could start to work really, really hard to not change data in the database.
  • We could start to think really, really hard up front before we build anything.
  • We could start to think about mechanisms that can reduce the number of things we need to communicate about.

The first two options introduce negative feedback cycles.  We spend more time in order to achieve them instead of less. And, given that things will change and we can’t know everything up front, we may make even more of a mess of the data when optimising for them.

Communication is important, so making sure the things that we’re communicating about are valuable is useful.  It keeps the signal to noise ratio high.  Starting to think of ways to reduce the number of things we are required to communicate about is useful, especially when that does not increase the time spent doing the new thing.  Ideally we should only communicate when the consumer must change.

Can’t we mitigate these problems?

We are smart developers, of course we can introduce mitigations!

We can introduce rules – like adding columns is always allowed.  If we are allowed to add columns that is one change we don’t need to communicate about.  This is a net win as most likely the consumers would generally not notice additive changes. We need to jointly make this rule as it is plausible that consumers could notice and fail.

The consumer could document what they are using.  If we can find out what is being used, then we can self-serve.  The only catch is – what if it isn’t up to date. Humans are fallible so this may happen. This solution moves the work that was done by the publishing team in needing to communicate to the consuming team as they always need to document.  But we’re still doing that work.  A failure may result in a negative feedback cycle – be better! Document more!  Spend more time achieving something that will fail any time a human forgets.

Any mitigation that does not involve feedback from code / tests will potentially fail at some point.  If we can introduce automated mechanisms that tell us that we’re breaking something we could get ahead of that.  Automation does not forget.  If it is setup to run, it will run every time.  If we can get automated feedback, then we know that we’re impacting and can get ahead of the game.

Contract Tests

A good mitigation might be to come up with solutions similar to those for HTTP APIs – perhaps some of the contract testing ideas could help.  For contract tests, the consuming team writes tests that test the API how they use it.  The team exposing the API cannot change these tests.  Their system needs to keep them alive.  This is a great feedback cycle for exposed APIs.  And then the two teams can negotiate about how their usage can change.

The negative part about defining a contract on the base schema of a database is that it represents the base model that we’re trying to build and invest in.  This is where we’re experimenting and learning internally to the team and the system.  Having that coupled to an external consumer makes that experimentation harder

Is there a better way?

Could we define the contract explicitly?  Could we make it work like an HTTP API?  At that point potentially all the rules and expectations that we have around HTTP APIs come into play.

An option could be to populate tables or provide views that the consumer uses. The internal team does not use these tables or views but signs up to them being the API contract for the consumer. This means that we can now apply all the standard mitigations around APIs that we are exposing outside of our system.  This becomes a known space to work in.  This does not make it easy, but it does define the deliberate place where we can support the integration to our database while allowing us to retain control and freely refactor the rest of the database as we wish.

A key idea around refactoring is to build the simplest solution that we can safely refactor out of.  Coupling to a defined, contained interface as a contract feels like a solution that can enable safe refactoring of the underlying code design while keeping the defined contract working.

What does this solution lead to?

Suddenly we have reduced the scope of questions we need to ask when we change the database.  We now know we are fine if the change in the database does not relate to one of the contracted tables / views.  Suddenly a whole category of friction is removed. We can write tests to give us feedback when we are accidentally changing those tables or views.  We do not use them in our systems so we have no reason to change them unless we are changing them at the request of the consumer. Another radical idea would be to use the Dependency Inversion principle and allow the consumer to define what the data should look like.  It is for them after all.

This sounds like a lot of work.  Can’t we just use database refactoring techniques to solve this?

The mechanisms for database refactoring are awesome.  They make your database more fluid just like code refactoring makes your code more fluid.

When you own all of the code, code refactoring flows in small steps that allow you to incrementally deploy software over and over again.  It is an awesome engineering feat.

When you own all of the database, database refactoring allows changes to the database to happen in small steps that allow you to incrementally deploy changes across the software and database over and over again.  It is another awesome engineering feat

Code refactoring slows down and has friction when the edge of the code that you are refactoring is shared by another consumer.  For instance an HTTP API or a class exposed by a Ruby Gem. The steps to quickly change the system run into friction around communication, planning and the potential that you might need to wait a long time before you can finally remove the interim code that is helping make the progression from one form of system to another

Database refactoring slows down and has friction when the database that you are refactoring is shared by another consumer.  The steps to quickly change the system run into friction around communication, planning and the potential that you might need to wait a long time before you can finally remove the interim code that is helping make the progression from one database design to another.

Database refactoring is really useful for helping understand how to change a legacy database shared by another consumer out of your control.  It moves it from static and scary to change, to slow moving and more malleable.

Database refactoring is really useful for helping speed up your changes in data when you own the whole stack.  It allows you to incrementally change the whole system in a far more fluid way.

Database refactoring doesn’t solve the communication and waiting overheads that come with co-ordinating with other consumers.  You can only clean up your refactoring as fast as the consumer follows your changes.  You still need to communicate about every change in case it has impact or introduce mitigations for that as described so far.

Another problem… data is not always meaningful without code

The data in an application’s database might need code to make it mean something to the business domain.  Keeping this in sync across multiple applications accessing the database directly can also lead to pain and violates DRY.  Deliberately providing data that has meaning is far more useful – whether that is directly into a database tables where that interpretation is potentially set or published out in an API that uses the code to do the interpretation.  The temptation here is to introduce a library to share to make sense of the data in the database.  But then we have to ensure every consumer is using the same version of the library…

Change is inevitable, how fast you embrace it is up to you.

I work in fluid environments that accept that code will change.  I accept that we don’t know everything.  We will change the systems we build as new knowledge and designs emerge.  Any team that needs to co-ordinate with another team to ask permission to make a change slows down.  There will be friction in refactoring at the shared edge of our systems.  I prefer to live in hope that we will be able to make more and more changes, to experiment and innovate with new products and ideas simply because we choose not to embrace solutions that will slow us down. Solutions that continue to be reoccurring work aren’t great.  Solutions that stop us from having to do a certain category of work speed us up. Work not done is time available to do other useful work.

Given a new or evolving data schema and database level integration – make sure you keep aware of the drawbacks and protect yourself.

  • Reduce the scope that is consumed.
  • Increase the feedback around what is consumed.
  • Inspect and adapt the pain that you do experience.

Hopefully these thoughts will help some others to avoid getting as many fingers burnt as I have in the past.

 

Know where you’re going

A core idea in agile software development is that we don’t know enough right now so we should only build enough for what we actually do know right now.  This is sensible.  This idea underpins evolutionary / emergent / test driven design – allowing them to thrive.  If we can safely and confidently change the system in any direction from where it is now, then we are in a good place.  We are keeping the XP cost of change curve as flat as possible.

But the cost of change may become high if we need to continuously rework everything. If the knowledge is available to help make more informed design choices earlier, we should use it.

Know where the code is going

Good software developers are intentionally trying to make the code base better.  This is highly desirable.  Good software developers will refactor confidently and allow new designs to emerge.  When you have 6 good developers refactoring the same code base into what in their minds is the best solution it is plausible that you may land up with 6 very different designs going in 6 very different directions.  This might not be ideal.

What would make those 6 good developers into a great team is a shared understanding of how the code is being refactored so that we don’t land up with 6 different designs, but rather one collaborative design that everyone has contributed to – pulling it in the same general direction.

As a team, discussing about where we’re refactoring the code to is important.  A given pair might not reach the best design while working in this sprint and another pair may augment and grow that design in the next sprint.  If we share where we’re going, we can hopefully get somewhere in the region of the shared destination.  And we can more meaningfully converse about changes in design as the requirements change and agree on the next destination.

But don’t inhibit innovation and new ideas

The counter to this is that if a good developer is building something and they choose to build something different to the current design – maybe it is simpler, better, cleaner – this should not be inhibited.  Do not strive for uniformity and accidentally crush the very innovation that you need to channel from a good developer in order to have a great team.

Know where the business is going

A common way of breaking up stories is around the CRUD screens / actions.  It is sometimes easy for a Product Owner to define a series of screens that allow them to administrate some set of information. It is plausible that these screens are built without a conversation of why the screens are being built.  Possibly this is because the PO doesn’t yet know the exact details on how these things are going to interact.  But if this is the case, it is plausible that when the PO finally specifies how these CRUD things are going to interact in a meaningful way that the team could turn around and suggest that, based on how those screens have been built, that isn’t possible – or at least it is very hacky and we’re already building a legacy system with bad design.

Knowing something about where the business is going in the next sprint or 3 is useful.  Use it to track how the design is evolving and hopefully take this at least somewhat into account when designing the code.

But don’t future proof too soon

Don’t worry too much about planning for future needs that are trivial to add in a future sprint.  We don’t need to future proof the design.  We only need to know where it might go so that we can perhaps go there more easily later.  We need to know that the short/medium term goals will be supportable by the design we are creating today.  If it isn’t, it doesn’t matter too much, but the cost of change will probably go up if we need to massively refactor the entire system every sprint – so what can we do to help avoid that?

Know where the architecture is going

Systems are continuously evolving.  As architectural issues arise, an idea of what should be done to solve them should be discussed.  If we can agree on the direction that we need to move the overall architecture and understand what it could look like, then any team may be able to start building that within existing work that they have, in an opportunistic way.  If we have no clue where we would like to go then any team will probably continue to do exactly what they are doing now.

Alternatively, any team may start to innovate in different directions and again we may land up with the architecture of the system being pulling in several different disparate directions which may not be a beneficial in the long run.

If your teams discuss and plan what the architectural changes could look like, then we know where we’re going and we can start working out the baby steps to get there.  Those baby steps may be started to be done inside current work.  But without any clue of where we going we can’t even start to take any baby steps.

But don’t be too constraining

Architecturally knowing where you’re going is important – but the micro details shouldn’t be constrained.  It shouldn’t matter if we use redis or memcache in terms of an architectural solution.  Let the teams converse and decide as a group on the actual solution details when they are needing the solution and allow that to be based on the real experience and constraints they are being experienced in their teams.

Know sort of where you’re going

The precise destination isn’t important – the general one is. The details will vary.  The overview of the destination over the next 2-3 months hopefully will grow and evolve.  Despite that, hopefully it will still be comparable enough to call reasonably the same.

How abstract the destination is depends on the need.  For instance, it might be useful to sketch out the boundaries of several micro-services to have some idea of the destination and to give teams an idea of what they could break out of a larger system.  But don’t be married to those decisions if they turn out to be incorrect.

Use the knowledge just in time

Knowing the destination today should inform the code as a potential destination to refactor towards.  We shouldn’t be focusing solely on the road directly in front of our feet.  We should be looking up into the horizon and gleaning any knowledge we can of what is up ahead.

Knowing where we are going will allow the whole team to pull the code and system in a similar direction.

Knowing sort of where we are going should lead to an informed just in time decision about when to do something with that knowledge.

DDD, Aggregates and designing for change

I have been introducing several domain driven design patterns to the teams that I work with over the last while.  In doing so I have been struck by the repetition of a core principle for many of the DDD patterns.

An example – the aggregate pattern
The aggregate pattern focuses on the business domain, attempting to answer the desire for a software developer to work with fully formed domain objects that interact with each other in a meaningful way.

The aggregate pattern defines the aggregate as a combination of objects that interact as a single unit.  The aggregate is only interacted with via the root of the aggregate.  This ensures that the aggregate root can maintain the integrity of the whole aggregate.

If all interactions with the aggregate are via the root aggregate this means that we can control the implementation of the aggregate behind the interface of the root aggregate.  The aggregate should be designed as a cohesive unit and is decoupled from the rest of the system via the aggregate root’s interface.  The aggregate root’s interface is the API to the aggregate.

Tensions
The complexity is in keeping the aggregate small while useful.  Tension exists when the aggregate gets bigger. The aggregate interface can become complex due to the constraint that all access must occur through the root aggregate.  This tension may push one towards designing smaller interoperable aggregates rather than allowing a large ball of mud to be formed.

Unidirectional?
Just as a design choice could be to have unidirectional models; it may also be an interesting design choice to build an aggregate with unidirectional models – with the root aggregate being the model that knows about (potentially) all the child models.  This may be useful.

My personal preference is to try to keep things unidirectional for as long as it is sensible, but as soon as it isn’t sensible allow connections in both directions.  The aggregate root is controlling the access to the objects in the aggregate.  Allowing objects in the aggregate to know about each other bi-directionally increases the complexity of the aggregate as a whole but, assuming a reasonably small aggregate, the principle of small contained messes is still supported behind the interface exposed by the aggregate root.

The core
Circling back to the core principle for many of the DDD patterns – the factory pattern, the repository pattern and the aggregate pattern all define mechanisms for creating a well-defined interface and hiding the implementation details behind the interface.

The factory pattern provides a creational interface to build an object.  We get well formed objects from it and don’t need to know how they were formed.

The repository pattern provides an interface that abstracts away object storage / query.  We ask it a question and get objects back.  We tell it to persist something, and it happens.  We do not need to worry how.

The root aggregate in the aggregate pattern provides an interface that abstracts away the implementation of the aggregate.

These patterns make code simpler and reduce complexity by clearly defining what should go behind the interface and what should not.  These patterns allow the caller to not worry about the implementation details behind the interface.  All of these patterns support the idea of smaller messes.  If the implementation behind the interface is a little messy, it doesn’t matter, as long as it can be refactored safely later and the caller is not influenced at all.

Decouple the interface that the outside world uses from the implementation underneath.  And know why the code is placed behind the interface.  Design a cohesive unit behind the interface.  This is also how TDD encourages code to be written, assuming you can design the interface well.

In practice
When discussing domain driven design versus other design ideas and using test driven design to build software, there are often questions around which pattern to use or how they should interact.  These questions are often attempting to get black and white answers to a complex contextual problem that probably has many shades of grey in it.

What has struck me most about introducing DDD after introducing TDD and emergent / evolutionary design is that the core principle is to contain the messes behind the interfaces.  Have good interfaces and decouple them.  And ensure the implementation behind the interface is cohesive and can change freely as needed.  That is the core of software design and most patterns.  How do I change later?  How do I keep in control of the code so that when change comes (and it will come! Especially in unexpected ways) it is not accidentally impactful on the rest of the system.

Focus on embracing change
Worry less about the patterns, than what the patterns are trying to teach you.  Use the patterns, understand them, they are a language that can be used effectively among developers.  But the patterns are not the end game, the changeable system that they encourage is.

Keeping it Simple – unidirectional models

Keeping code simple is an art.  It may require a reasonable amount of work – refactoring and trying design experiments to keep it as simple as possible.  Changes to requirements may result in the current design no longer being the simplest thing any more.

One way to attempt to keep things simple is to reduce complexity by not implementing anything that a current use case does not need.  That way, changing the design only has to worry about code that is being used – there is no guessing game.

Another way to attempt to keep things simple is to reduce the paths that can be taken through your code base to reach a given point.  Making small cohesive units that can be reasoned about with a well-defined interface to the rest of the code base ensures you can change those cohesive units more easily.

Adding complexity

A way to increase complexity is adding unnecessary code.  This is often done because “it’s obvious that it is the right thing to do” or “we’ll probably need it” rather than driven from any actual use case.

An example of this can often be seen when using an ORM.  Given a model that is composed of attributes and a list of another type of model, a common pattern is for both sides to know about each other.  The parent model will have a list of children and the child model has a reference back to the parent model.  This is particularly obvious when the design is data driven (driven from the database tables) and the designer feels comfortable just grabbing any table / model and loading something from it and working from that view of the data.

A classic modelling example is an implementation of an Order.  An Order may have many Line Items in a list.  In the standard way that typical ORMs encourage you, the Order model will know about its Line Items and a Line Item will know about its Order.  This means that I can load an Order object and expect to be able to access its Line Items.  It also means that I can load an individual Line Item and directly access its Order.  And then I could use that Order to get the Line Items, one of which was the original object that started the request.

What’s wrong with that?

In the simplest implementation with limited complexity this may be fine.  But it could lead to some problems as the complexity of the solution grows.

Cyclic dependencies

One problem that can arise is cyclic dependencies – where the parent can call a child and the child can then call the parent and around we go.  This may be hard to reason about – particularly as the object graph grows in size.

Maintaining a cohesive world

These convenience methods increase complexity.  Having a design that can be entered from anywhere and needs to remain cohesive in any direction from that entry point increases complexity.  Additional code may be written to compensate.

It can lead to needing the world to be cohesive no matter which object I load and manipulate.  By allowing the Order to know about the Line Item and the Line Item to know about the Order, allows the option to add a Line Item separate from an Order, which may not be valid in the domain.  We may always expect a Line Item to have an Order and this could be violated.  In order to resolve this, we need to add validation on the Line Item to ensure there is an Order.

A potential design may be that changes to a Line Item may expect changes on the Order model.  If we can load the Line Item first, then we need to solve the consistency of the Order model.  Clearly we should add callbacks when the Line Item model saves in order to ensure the Order is up to date.

Maybe we have a tax amount stored on the Order.  When this updates, all Line Item’s need their totals to be updated due to the change in the tax percentage.  Clearly we should add callbacks when the Order saves to update this.

If I can access a Line Item directly, I can also change its total.  This may require the Order’s total to be updated.  Clearly we should add callbacks to update the Order when the Line Item is saved.

Now I’ve written a bunch of callbacks in order to keep my domain valid because I can load anything and use it.  I also may have introduced a cyclic dependency and a cascade of DB updates that I don’t control very well.  When I save the Order, all Line Items will be loaded and saved.  Each Line Item may change the total value in the Order… and if we do this badly we’ll get stuck in an unexpected loop.

Increased testing for unsupported use cases

In an ideal world, every use case that you build should have a test.  Putting in the back connections between two models should be driven by that use case.  If you don’t have a failing test case, don’t write the code.  If you don’t have a use for the code… don’t write the code.  But if you’re writing the code, write the test.  Which increases the testing burden – for unsupported use cases.

Containing complexity

Instead of providing inconvenient convenience methods, why not contain the complexity and not provide it?

Unidirectional linkages

For a start, could we make all models talk in only one direction?  What would that do to the code?  Suddenly we have reduced complexity.  In order to load one model, it can only be accessed one way.  There is no expectation of the other way.

For instance, assume that the Order knows about its Line Items.  But the Line Item does not have a back reference to the Order.  What would that mean?

A Line Item can never be created without an Order.

A Line Item is updated through the Order, therefore any consistency that the Order needs to maintain due to changes in the Line Item are simply done in the Order.

When the Order’s tax is updated, we now have a design decision – do we update all Line Items at Order save time?  Or do we update Line Items as we pass them out of the Order?  Suddenly we have control over the performance characteristics of this update.

The Order / Line Item relationship is now easier to maintain and reason about as the expectations of the relationship are simpler.

Line Items may still be read directly, but the design states that there is no expectation of accessing the Order.  In order to access the Order, use the order_id that may be in the Line Item object to directly load the Order and get a fresh object.

But that is more work!

Suddenly we have a little more work.  The little more work is clear and obvious.  We can’t load a Line Item and expect it to just get the Order.

I would argue that the backlinks have similar (and potentially significantly more) complexity – but that isn’t visible until later when you realise that the Order needs updating when you save the Line Item that you loaded directly.

Not putting in the back links acknowledges the real work to be done and the thinking required instead of pretending it isn’t there.

Let’s go further – the Aggregate Pattern

What would happen if we defined a model like the Order / Line Item relationship, but only allow any interaction to the Order / Line Item cohesive unit via the Order?  What would happen if when we interact with the model, it was always fully loaded and well defined and hence our expectations would be well defined.

This is the aggregate pattern, a pattern popularised by domain driven design.  The Order model would be the root of the aggregate. A Line Item would not be accessible to the domain except via the Order.  A design choice could be to only allow new Line Items being created via the Order.  Suddenly we have control of all interactions with the models.

There is more to the aggregate pattern and there are more implications and ideas, but I’ll write on that another time.

Keep it simple

Why not try to keep things simple instead of assuming things are necessary?  Experiment with patterns that have “extreme” ideas, as maybe there is something to learn.  Choosing constraints in your design that help simplicity and reasoning – even when it means slightly more explicit coding – can be a liberating thing.

Maintain your confidence in the face of change.  Keep it simple.

 

A potential caveat

Maybe your ORM needs the backlinks to do some of its magic.  If that is the case, it feels like it might be unexpected.

Discoverability – A naming choice

When reading or refactoring code, it is important to be able to easily find all callers of a method or to go to the implementation of a method.  In static languages, IDEs make this reasonably easy.  However, if reflection is being used, they may still fail us.  It is simply easier to parse a static language to know the types involved and find the right ones to tell us about.  In dynamic languages that is much harder.

In a dynamic language, a key way to find all references to an object’s method call is “Find in Files”.  This means what we choose to name things may make it harder or easier to change later.  It may also make it harder to discover who is calling the method – or even that the method exists.

A unique name

In order to refactor a uniquely named method on a class

  • search for the method name as a string
  • rename

As we know it is unique, this will work.  In fact, you might be able to run a simple find and replace in files instead of looking at each result individually.

This scenario however is unlikely.  At least it is unlikely that we emphatically know that a given method name is unique.

A more likely scenario

In order to refactor a descriptively named method such as full_price_for_tour on a Tour class

  • search for the method name as a string
  • in each search result – check all references to the method name to see if they are in fact using a Tour object
  • if this is a Tour object call, rename the method call to the new name.

This is more work as we need to look at each result.  Hopefully with a descriptively named method the number of usages will not be too high.  Even if the number of usages is high, hopefully all usages of the name will in fact be on the Tour class.

However, we do need to look at each result as this process is potentially error prone.  There could be other method definitions using the same name that we need to NOT rename.  Hopefully there are tests that will tell us if we fail.  And hopefully the number of callers to change isn’t too high due to the descriptiveness of the method so the changes to the callers is clear.

Sometimes the results are less simple

Now imagine repeating the above exercise, but now the name of the method to refactor is name.  Suddenly we may have a huge number of hits with many classes exposing a name method for their instances.  Now the ratio of search result hits that are to be updated is no longer almost 100%.  The probability of error is much higher – the greater the number of hits, the more actual choices that need to be made.

An IDE may help

Immediately the IDE lovers will point out that, using an IDE is the solution.  And yes, it could help.  But IDEs for dynamic languages are generally slow and CPU/memory intensive as the problem is a hard one to solve.  And they won’t always be correct.  So you will still need to employ strategies using a human mind.

Naming things more explicitly can help

A more useful model – even if you’re using an IDE – is to name things descriptively, without being silly.  Things like tour_name and operator_name instead of name may help someone discover where / how a method is being used more easily.

Designing code to only expose a given interface can help

Building cohesive units of code that only interact through a well defined interface makes changing behind the interface a lot easier.  However it still doesn’t discount developers reaching in behind the curtain and using internals that they should not.  So you will still need to check.  Hopefully code that breaks the design like this will be caught before it gets merged into the mainline, but you never truly know without looking.

Reducing scope where possible can help

Knowing the scope of access of the thing you need to change can make changing it easier as it reduces the area you need to look in.  For example, if something is a private method then we know that as long as all usages in this class are updated, then we are completely free to change it. Unless someone has done a private_send from somewhere else…  Or we are mixing in a module that uses the private method from there… Both of which I’d like to think no one would be that silly to do.

Testing can help

Obviously having a comprehensive test suite that calls all implemented permutations of the usage of the method will help to validate a change.  It will hopefully help us discover when we have missed updating callers.  Or when we’ve accidentally changed a caller that shouldn’t be changed.  However if there are name clashes for the new name, it is plausible that it might not give you the feedback that we expect so it isn’t a silver bullet if you aren’t naming things well.

Think! 

Think about naming.  Think about discoverability.  Is there something that will make changing this easier in the future?

Think about the cost of making discoverability harder.  Be aware of the implications of a naming choice.  Is there something that can be done to make it easier to safely refactor away from this choice later?

Can we make things worse? Discoverability is a design choice to make easier or harder.