Simon Sebright's Home Page Home Contact


Proactive Laziness

Introduction

This article describes a pattern I have recognized in real life as well as development process and implementation.

Proactive Laziness refers to doing something up front to avoid problems later in time. The key thing is that the up-front activity is something different to the downstream activity, it instead facilitates it. As such, it is different to the pattern which might be called "Keeping on top of things".

For example, we all have to pay bills. Paying a bill early is the same activity as paying it too late, so does not count (even though paying late might have penalties attached) as Proactive Laziness. Rather, it is simply keeping your affairs in order. However, setting up a direct debit is Proactive Laziness, because it is a different activity. In this case, the up-front activity automatically ensures that the desired result occurs.

Of course, without the Proactive Laziness device, in the real world, we can have benefits simply by keeping on top of things – making payments, opening post, buying enough milk for your tea. In the development world, this keeping on top of things can become difficult, if not impossible, and has a tendency to distort. For example, not using RAII classes means tracking all exit points of a function, notwithstanding the fact that exceptions can occur in some languages.

Often, the upfront activity requires more effort than the initial problem it is avoiding, but has the ability to solve the problem multiple times, thus as time goes by, the payback period is exceeded and we start to gain.

I want you to go away from this article thinking what you can do better in your work, where you can save time and money by putting in place practices which help you help yourself. Things which mean you can leave for home in the evening with a feeling of confidence, and drink your beer in peace.

Be Proactively Lazy!

Domains

This is an article for technical people, so most of the domains are to do with software development. I also look first at a few real-world examples to show that this pattern is not purely about writing code. I then move on to look at Development Process, Development itself and lastly User Interfaces.

Mechanisms

For each example, I have identified the main mechanism underlying its ability to be proactive. It turns out that there are common mechanisms in the domains identified:

Availability

Having information available when needed

Automation

When something occurs automatically - we set something up, and it happens how and when it should.

Deterrent

The next best thing to Enforcement, where we _try_ to stop bad things happening

Enforcement

Stopping the things we don't want to happen from happening, usually by design

Flexibility

Offering components which when combined together create more numerous, powerful and elegant solutions than otherwise possible when functionality is locked up in one entity

Real World Examples

Here are some things you may or may not do in day-to-day life which fall under the practice of Proactive Laziness.

Direct Debit Payments

As mentioned in the introduction, taking the time to set up an automatic payment mechanism means you will never forget to pay your bills on time and thereby avoid incurring penalties.

Mechanism

Automation - automation of the desired payment activity. You rely on the systems of your suppliers and financial institutions to make the correct payments at the correct time.

Risk

You have to trust the suppliers and financial bodies controlling the transactions. Personally, I am quite happy with the service I have had over the many years I've been using it.

Store Phone Number in Address Book

We often need to ring new people. If you have the number either on screen, a business card, or just a piece of paper, you need to type it into your phone. Taking the time to add it to the address book before you call the person means that you won't have to remember it anymore, or take the time to retype it, or risk losing it. (Often a mobile phone will keep a record of calls made, so you might be able to retrieve it later, but they usually drop off the list after time).

Mechanism

Availability - we always know we can retrieve the phone number whilst using the device in question.

Risk

Of course, you could forget the phone with the number stored on it, change the chip, it might break, etc. The number may also change; only a central updated repository can deal with that.

Shopping List

If you go shopping for something, particularly household shopping where many items need to be purchased at once, then make a shopping list. That way, you don't have to remember everything and run the risk of forgetting things you need, or wasting money on things you don't.

It's best to make the list as the time goes by from the last shopping trip, as it becomes apparent that things will be needed. You might have another fixed list for things you always need such as milk, eggs, bread, etc. to avoid clutter.

Mechanism

Availability – the list of things needed is collated in one place. An alternative is to go round the shop looking at everything deciding whether or not you need it. The latter works better for people living alone!

Risk

List could be lost, difficult to read or ambiguous. Also, if this list is rigidly applied without intelligence, we don't cover the cases of lack of availability, special offers, spontaneous decisions on meal plans, etc.

Application in Software

This can be a way to introduce performance increases in applications. A computer does not get bored if you tell it to visit every item and see if it's needed, but as the number of items increases, so does the time to find out. If a process keeps a separate list of things to process, then if that list is a small fraction of the total available, we can potentially save a lot of time.

Development Process Examples

These are things we do in the process of producing software. It might be part of a methodology or an actual activity. The general aim is to make sure that the process is robust and reproducible.

Turn up Compiler Warnings

The compiler is your friend. It will tell you when you are doing things which might cause errors in your program. Turn up the warning level to maximum. Ideally, do this at the start of a project and compile with no warnings. I recommend even in the middle of a project that this is a good practice and have often taken the time to eliminate warnings. Comment out unused parameters and use appropriate casts where truncation may be occurring (or change the datatypes).

One problem here is library headers. Warnings coming from them are not in your control. Some environments allow you to introduce #pragma statements to suppress these. Do not suppress warnings globally in your own code though (apart from rare annoying ones that can never be relevant for you, for example the Microsoft performance warning on converting BOOL to bool).

That time when you don't refer to a caught exception local variable might be a bug – as you won't be referencing the nature of the error. That time you don't reference a parameter might mean you incorrectly implement a function.

Mechanism

Enforcement – you use a tool which tries to limit your misuse (intentional or otherwise) of the language.

Risk

You bypass it by ignoring warnings on too large a scale with #pragmas.

You become blasé about putting in C-style casts whenever a narrowing conversion or similar is diagnosed.

Treat Warnings as Errors

Stop yourself cheating, or new colleagues unknowingly breaking your clean build. Tell the compiler to treat warnings as errors, and you simply won't be able to build a non-clean code base. Again, it is best to do this up front and have a clear strategy for library files.

Mechanism

Enforcement – like turning the warning level up, only this time, you are not allowed to proceed without warnings being addressed.

TDD

Take the time to introduce tests which your code passes as you develop. In terms of Proactive Laziness, this means you have a much better time when you make changes to the code, including refactoring. You don't have to manually run through tests, or analyse in so much detail to be certain that you haven't broken anything.

In addition, you have provided some level of documentation of your code, and may find that by writing it from a client perspective, that the interfaces are cleaner and easier to use than by starting to write functionality in the cpp files.

Mechanism

Enforcement – the code has to pass the tests to proceed through the process of check-in and build. Ideally, the build process should automatically run the tests and fail if any test fails.

Automatic Builds

By writing a script to get from source control and build your releases, you eliminate the risk of forgetting to check in files, or having local copies of extra files which affect the result when you test your own code on your machine.

Only ever release (even to internal recipients) products which have been compiled from a labelled version of the source code. This way, you can always be sure what they got, and can repeat it in the future.

In addition, incorporating automatic tests as part of the build detects problems early, particularly it might pick up on issues caused by code integration, if developers have not been building their changes against the latest codebase.

Mechanism

Enforcement – you cannot deliver a version of the product if the files have not been checked in properly, or if the code has build errors

Risk

In some cases, missing code may not cause a failed build, particularly where classes perform some kind of self-registration. In this case, automated tests will help, because they should cover all the cases your product is designed to handle.

Development Implementation Examples

Finally, when writing code, whether the design of core interfaces in a large system, or the minutiae of a particular function, do what you can to prevent things from going wrong, being misused, etc.

Encapsulation

This is one of the classic tenements of OO (Object Orientation). Objects are instances of Classes, and classes can have both data members and function members (in some languages Properties and others too). This means we can bundle pieces of data with the functionality required to manipulate it, and stop other functionality misusing it. We only expose the functionality we want the client to use, thus abstracting the implementation details away.

Taking the time to encapsulate data and functionality means that you know who can and can't use it. When members become non-public, you can write your code with certainty that abuse is difficult. At least your intention has been clearly signalled, if someone does nasty things to get at your data, it's their own fault if things go wrong.

Avoid set functions "just for completeness", have the interface of a class truly reflect what you want it to do, not what it could possibly do given the data members you have. See the Easy to Use, Hard to Break item below.

Mechanism

Enforcement – you cannot access member data or functions which are marked as private, and only derived classes will be able to access protected members.

Risk

It may be the case that some functionality hidden from clients may at some future point really be needed. Particularly where frameworks are built, and therefore the end functionality is not known, it can be tempting to expose more than is strictly necessary. For example, the framework MFC is notorious for having protected and even public data members. The wider the applicability of a framework, the more this is likely to happen.

Of course, if you are writing your framework to use yourself, you can keep things as tight as possible until particular requirements arise which change things.

RAII Classes

RAII (Resource Acquisition Is Initialisation) refers to using the scope/lifetime of objects to automatically control access to a resource. In this case, the "resource" is anything which might need special handling, usually with respect to clearing up when we have finished with it, for example, freeing up memory, closing a file, releasing a mutex, etc.

In a language with deterministic destruction, like C++, one can wrap pairs of function calls in a class, one occurring in the constructor, the other in the destructor. For example open/close, lock/unlock, new/delete. These symmetrical pairs then automatically get invoked when an object is constructed in a scope, and when it is destructed at the end of scope

In an exception-enabled program, they are in fact required for correct function, because you can't be sure that you will ever reach the line of code which does the clearing up.

In a language with non-deterministic destruction such as C#, you can use the using construction. This is a lot messier than the C++ constructor/destructor mechanism because you have to implement IDisposable. This interface only has the Dispose() function on it, but to implement it properly involves being aware of finalizers being called multiple times, and taking into account the fact that the client may forget to use the using construct at all. One nice feature of the C++/CLI language here is that the C++-style destructor of a managed class automatically gets the using construct equivalent set up, and automatically implements IDisposable properly, so you can use it from other.net languages.

Mechanism

Automation – by simply declaring an instance of a RAII class in a scope, you automatically get the resource cleared up when and however the scope is exited.

Risk

These classes are intended for use on the stack, so that the stack unwinding mechanism at scope exit invokes the destructors. Someone might allocate an instance on the heap, and it may then not get destructed, and probably at the wrong time. You can protect against this by overriding operator new for that class to either be not implemented/private, or throw an exception or anything else which would cause the misuser to struggle.

Easy to Use, Hard to Break

[Parkin/Meyers] When developing a class, you must strive to make that class easy to use. That is understandable – you want the clients to adopt it, and therefore having a clear interface, with minimal complexity is a good idea. The functions they call should be as obvious as possible, and you should strive to avoid compulsory sequences. For example, use a one-stop constructor rather than a default constructor followed by an initialisation function.

Hard to break takes this a step further. You want to minimize the chance that your users will either inadvertently or deliberately misuse your class. Therefore, take the time to protect it from improper use.

Devices include cutting down the interface to the bare minimum, or not publicly inheriting from an implementation-biased base class. Derive from NonCopyable if you don't want your objects to be copied.

If it's a base class, make the constructors protected and make the destructor either pure virtual or protected.

Where the language supports it, make classes either abstract or sealed so that correct class hierarchies are enforced. C++ users can make the destructor pure virtual to designate an abstract class. Marking constructors and possibly the destructor as protected achieves a similar effect.

Be careful of implicit conversions and make constructors explicit where appropriate.

For value classes, be sure to implement the expected functionality in a standard way. Constructors, assignments, etc. should be canonical in form, "Do as the ints"!

Proactive use of const on both method signatures and parameters will help to avoid inadvertent change of state where not necessary, as well as giving clear indication of intent.

Mechanism

Enforcement – by deliberately restricting the things someone can do with your class to those things which it intended to do, you enforce correct use. In addition, by offering an interface which is clean and easily understood, you encourage people to use this class and not attempt to roll their own.

Risk

If the interface is too restrictive, people may be put off using it. In the case where there is an alternative (for example direct access to an OS API), this may be worse than having a more powerful wrapper, because they will stick to what they know, with all the inherent risks of memory mismanagement, etc.

Refactor

How many times have you seen code copied and pasted? Here is a prime chance to factor out a common function. Doing so takes the risk out of copying that code incorrectly, or needing to make future changes in all places.

Other forms of refactoring also fit the Proactive Laziness bill, particularly if you have a test suite built up.

As I mentioned in an Overload article [Sebright], factors such as having access to files in the source control system can be a hindrance to this kind of activity, particularly where functions need to be added to header files, or new files added to the project in the case of a new class.

Mechanism

There is a mixture of ways in which refactoring helps. For example factoring common snippets into a function acts a Deterrent of error when coding something needing it next time. This is a lesser form of Enforcement, because you cannot stop the developer copying and pasting again.

Risk

Any code changes pose a risk. Incorrect implementations or simply reaching some limit on something may cause failure. Having a set of tests for your code will help to stop this. Indeed, when used as part of TDD (Test-Driven Development), Refactor is an encouraged part of the process.

Separation of Algorithm and Data

C++ developers will know (or should know) stl (the standard template library). One of the mainstays of this is the relationship between containers and algorithms. By abstracting storage behind a common interface (use of iterators), we can write any number of algorithms to do things with ranges of data. For example, we can sort, find, and perform calculations based on a sequence of data.

This may seem at odds with the idea of encapsulation, which tries to keep functionality and data together. It isn't, though, because the actual storage mechanisms of the various containers (e.g. vector, map, string), is still encapsulated, we just offer a common interface for running through the sequence of elements in the form of iterators.

Thus, developers have a handy library of functions they can apply the containers that come with standard stl, or containers they may write (through the use of the iterator concept, a little like writing an adapter for a class). In addition, algorithms can be compounded to make more powerful functionality

Mechanism

Flexibility – this is achieved through separation of concerns, allowing us to combine things more freely.

Risk

Having generic algorithms could lead to inefficiencies if the data structure could be more efficiently operated on with greater knowledge. To combat that, certain containers might offer their own versions of specific algorithms. std::map offers find() for example.

User Interface

HCI (Human-Computer Interaction) is a large subject. There is much literature on usability, but some points in particular are relevant to this discussion, because some effort from the UI designers, or developers can help prevent the user getting into error situations, thus saving them and your support function time and effort.

Poka-Yoke Devices

Taken from the Japanese car industry, this concept (pronounced " POH-kah YOH-keh") is a family of mechanisms which seek to stop error conditions happening in the first place. The Toyota car manufacturing company sought to find ways to reduce the number of mistakes happening on the production line. They identified common problems (such as missing bolts, welds not made, etc.) and then thought of mechanisms by which these could be spotted up front. Then, the situation was fixed at source, avoiding costly downstream action.

We can take the same idea and apply it to user interfaces. If there is a piece of data which has to be in a particular format, ensure that is the case before processing it. E.g. if the user needs to enter an integer, you can restrict the characters enterable into a text field to those. Masked text boxes can be used to ensure data is in a pre-defined format. Or, one step laxer, you can validate the fields on a form before accepting it. When done best, this does not let you submit a form which contains incorrect data. It should be accompanied by a suitable help message somewhere (preferably close to the source of error) to let the user know what they have done wrong.

Mechanism

Enforcement – you make sure the user can only enter correct data before attempting to process it.

Risk

You can be over-restrictive if you don't analyse the situation well. For example, expecting telephone numbers in a country-specific format is bad if that application is to be used by people abroad. Similarly, not all countries have a zip or postal code, and they are not all the same format.

Undo/Redo

This is now something users expect in a normal product running on their machines, but is only just becoming more widespread in web-based applications. It allows the user to backtrack a sequence of actions, and if so, to replay them again. These actions could be editing a document, or navigating to a different web page in a browser.

Mechanism

Flexibility – by allowing the user to explore in the confidence of being able to get back to where they came from

Conclusion

We have seen several examples of Proactive Laziness; where we take steps up front to think how we might do something better or avoid problems in the future. When we do so, we set in place more security about how things will be executed or used in the systems we are involved with.

There are many more examples in many more domains. However you work or live, have a think what you can do today that saves you time not just tomorrow, but the day after and the day after that…

References

Parkin/Meyers – Semantic Programming, ACCU 2007 Conference by Ric Parkin, referring to one of Scott Meyers' Item 18 in Effective C++ - Make interfaces easy to use correctly and hard to use incorrectly

Sebright – Up Against the Barrier, Overload 75 October 2006


Copyright Simon Sebright 2007