Sunday, 25 September 2016

Agile Quality



Being an agile team is not achieved by flipping a switch, becoming agile is a journey where the validity of qualifying yourselves as being agile is a sliding scale.

Along with an adherence to the structure and ceremonies of your chosen agile approach there is also a required mindset that needs to be adopted in order for the benefits of agile to be realised.

So if being agile is not a binary metric how do we know in our day to day working lives if were moving in the right direction towards our agile nirvana.

Story Writing

An agile story is the communication device by which stakeholders can communicate to the team the outcomes that need to be achieved to implement some desired functionality.

As with any form of communication its possible to do this badly, to generate misunderstanding and confusion that ultimately lead to a failure to deliver or a delivery that provides the wrong outcomes.

Story writing should not be an arduous process, it should promote discussion but it shouldn't cause confusion.

A key to this is realising that story writing needs to start only when a broad understanding of outcomes is already in our grasp.

The process of story writing should be for the team to create an understanding of what needs to be done and the approach that will be taken, it shouldn't be a painful process of trying to tease out of stakeholders what the point of doing this work is.

You can learn a lot about agile quality by watching a team in a story writing session, are you watching a well oiled team who understand the product they work on and where its going? Or are you watching a group of individuals churning out software without an understanding of why?

Backlog

Once a certain number of stories have been written then you have created a backlog.

Again a backlog can be seen as a form of communication, it describes the journey a product is going on and identifies the signposts and milestones that need to be hit along the way.

A good agile backlog is fluid, the next priority can be changed, stories can be added, removed and re-ordered.

What shouldn't be fluid is the over arching themes of what is trying to be achieved, the product that is being built and the path of its evolution.

A low quality backlog will not demonstrate this cohesion, it will present itself as just a collection of work items thrown together in order to give a team something to do. It will not have obvious goals or points of delivery.

The quality of a backlog can often been seen by asking a member of the team to talk you through the stories, what they achieve, why they are in the order they are and where a deliverable exists.

Delivery

The point of all this is to deliver software and almost regardless of what development philosophy you apply the effectiveness of a team should be measured in their ability to deliver working software.

However we need to understand what is realistic to expect.

No matter the quality of a team the ability to deliver any amount of scope in any amount of time is a fallacy.

What should be valued is the predictable nature of a teams delivery, this predictability will come from an accurate and stable velocity.

When this is combined with a quality backlog made up of quality stories this enables accurate estimation.

A stable velocity can only be achieved when a team, whose members don't change, is working its way through a well crafted backlog towards a well understood goal.

Velocity should be the third measure of the agile quality of a team, as the team improves its practices it should demonstrate an upward trend but not have large variation between every sprint, within the short term past performance should predict future delivery.

Ultimately all these aspects of agile combine and interact, the important aspect is to embrace the agile mindset and realise it is not a project management process.

It is a living organic approach that embraces change and wants to deliver what is required on regular and improving cadence. But as with any system, garbage in will lead to garbage out.

Sunday, 18 September 2016

Extend Don't Modify


One of the five pillars of SOLID software engineering is the open/closed principle:

    A software entity should be open for extension but closed for modification. 
 
With the advent of distributed source control systems, such as Git, many claim that the open/closed principle (OCP) has lost its relevance. We don't need to fear modification since we can accurately track change, roll it back if required and visualise the history of a piece of code.

I argue that this is looking at OCP in a limited sense, software has an inherent ability to be fragile and therefore any architecture or implementation that promotes modification is on a path to increase this fragility and ultimately lead to defects and instability.

Concentration of Knowledge

Regardless of whether or not we can track modification we should still ask ourselves why we are required to modify and not extend a class, often this is because we have created a concentration of knowledge and therefore a concentration of change.

If we have a class responsible for drawing shapes we will be forced to change this class every time the number of types of shape in our universe expands, if we instead delegate responsibility for graphics to each individual shape then we can naturally extend this universe just by adding new shape classes.

The above example is generated by the fact that knowledge of how to draw shapes was concentrated in one place when the number of shapes we have to work with was going to be on an upward trajectory.

Sometimes its a fine line between obeying OCP while also adhering to other SOLID principles such as Single Responsibility Principle (SRP), however both principles are trying to limit the need for change, if we concentrate knowledge in a particular class we need to keep in mind how much that knowledge may need to expand.

Too Many Cooks

Another important aspect of OCP is a much more practical one.

Source control systems may be good at tracking change but they cannot smooth the problems we encounter when many engineers have rolled up their sleeves to go under the hood of a piece of code.

Creating a code base where many developers may be working in the same area will have an impact on velocity both in terms of developers struggling to get their code merged in, struggling to understand an area of code under modification and with the defects that will arise as a result.

Sometime we concentrate solely on the properties of code that can be slightly abstract in nature but the ease at which a code base can be worked on by a team is something that should not be forgotten or ignored.

OCP helps create a code base where many developers can work together in harmony without tripping over each other and without creating confusion.

This Used to Work

Software has the potential to be inherently fragile, it is written by developers who are only human and will make mistakes.

We put certain automation in place to try and catch these errors but they still on occasion make it through.

Promoting change in already working software should be left for refactoring activities to improve the overall quality of the code, it shouldn't be a daily occurrence because the software has been built in such way that it must be deconstructed before being re-built with new functionality on every iteration.

The ideal situation to be in is that whenever new functionality is added the risk of defects only exists within that new functionality. It shouldn't be the case that all bets are off on the working state of previous functionality because its implementation has been modified or changed to accommodate the additions.

The SOLID principles are not always universal truths, there are occasions where strict appliance would not improve a code base, but that needs to be viewed in the round sometimes the benefits they bring can be both broad and subtle.

Although the invention of many of those principles pre-dates the world in which we are developing software the truth within them still holds, how to engineer good software hasn't changed that much and its unlikely any of those five principles will ever be declared defunct.

Monday, 12 September 2016

Feature Quest



When working on a software project it can sometime feel like your part of a never ending convey belt of feature delivery.

Its easy to fall into a trap of becoming obsessed with the delivery of more and more features for users and to value this above all other aspects of the product.

Actually there is more to developing a product then just bombarding users with a constant stream of shiny new features.

When you come to think about it, how often do the major platforms such as Google, Facebook, Apple or Amazon release new features that are visible to users? Its not as often as you think.

Working is a Feature

Sometimes we assume that users are judging us by the cadence at which we are giving them new toys to play.

Generally if you ask a user "would you like it to do this..." you will get some form of positive reaction, however if you ask them what bugs or annoys them about using your product they will talk about problems they have using the current feature set.

Performance and stability need to be viewed as features that are just as valuable as the next bright idea, users have very little tolerance for a decline in either of those two aspects of your product.

You are much more likely to lose users because your product is not functional or doesn't do what it says on the tin then you are to boredom of a perceived slow down in new features.

Quality in your code base is not a nice to have, ultimately its what users value and no amount of bells and whistles will distract them from it.

Internally Facing Features

We don't necessarily have to view the features we deliver as being always focused on the user.

In our own way the organisations we work in are consumers of our product, we have to work with it, deploy it and maintain it.

Features can address those key areas as much as they might have an impact on things the user is aware of.

Although working on these areas might be seen as making our lives easier they also have an indirect knock on affect on our users.

Anything that makes our life easier makes delivering software easier and easier delivery means we can make more frequent updates and in turn deliver to the user at a higher rate.

When evaluating potential new features we need to look at the whole delivery chain not just the relative tip of the iceberg represented by what is visible to the user.

Feature Overload

Pick any popular piece of software and ask people what they use it for and in the vast majority of cases that list will be relatively small and not include everything that software is capable of.

It is also equally likely that if you compare how the features on the list work now to how they worked in the original version you will see a large progression.

If we take an agile iterative approach to development we should develop a mindset of "better and better" not "more and more".

We should concentrate on the core set of features that we feel our the prime value drivers in our product and work on ways to deliver that functionality in an ever increasingly better way.

These improvements may be marginal but by continually delivering them in a relatively short amount of time they can add up.

A performance tweak here and a bug fix there, some polish to the UI here and some smoother UX there can all make users feel like your product is more useful in their lives.

Sometimes we develop a features at all costs attitude that does more harm then good and is not really want users want.

Users want the functionality that originally brought them to your product to work and work well.

In time they come to expect you to offer them more but they don't expect this at any where near the pace that we sometimes think they do.

When the time for new features does come it should be an organic extension to what you already offer without the need to re-invent the wheel or find the silver bullet.

Sunday, 4 September 2016

Flipping Dependencies



Sometimes we can be guilty of following a principle or a pattern without fully understanding or appreciating why this is a good thing.

Some could argue what difference does it make if your writing good code? But without understanding what makes the code good you invite the risk of unintentionally degrading it.

One of these patterns is dependency injection, sometimes developers will implement this pattern without appreciating that the reason for doing it is to apply the principle of Inversion of Control (IoC).

Static vs Dynamic

A class that does not apply IoC is statically bound to its dependencies, they are defined when the class is compiled and will never change without the code being changed.

When a class is written to apply the IoC principle it is dynamically bound to its dependencies they are not necessarily defined at compile time, instead they are loosely coupled by a shared contract in the form of an interface between the dependency and the dependent.

The major point here is that we have inverted who is in control of defining which implementation will fulfil the dependency for the class we have written. Rather than the class itself controlling this the framework in which the class operates is in control.

So why is this inversion a good thing?

Hang Loose

The primary benefit of IoC is to create loose coupling between classes.

The benefits of this to the dependent class should be clear, it makes the class eminently testable and increases the opportunities for re-use.

It is almost by definition not possible to test a class that is statically bound to its dependencies.

A unit test should only have one reason to fail, namely that the class being tested is not supplying its required functionality. It should not be the case that the failure is caused by one of its dependencies, this would mark this test as an integration test, this doesn't mean the test has no value but it does make it harder to interpret the results.

If its impossible to break a class away from its dependencies then its impossible to write a unit test.

Quite often despite the fact that a class has the control to define the implementation of its dependency it actually has no reason to have this control, it has not got a dependency on the implementation only on the functionality.

By applying IoC we properly model this situation and allow the class to function with any implementation willing to provide the desired functionality, we have made the class more flexible, re-usable and ensured we do not have to needlessly write more code or copy code we've already written.

Loose coupling also has a benefit to the class supplying the dependency, by being statically bound to an implementation we don't allow for any change in that implementation without the requirement to transmit that change to the dependent classes.

By dynamically binding to the functionality we allow for change in implementation to have no impact on the dependent class allowing it to carry on working in the way it did before. 

Giving Up Control

Once a class is no longer responsible for creating its dependencies it can also be absolved of any responsibility for implementing logic relating to that creation.

The IoC principle allows our use of constructs such as singletons, object pools or prototypes to be both hidden from the class that needs the functionality and centralised within our implementation of a container to realise the IoC principle.

This is another example of how application of IoC is all about leaving the class that requires dependencies to be written with only concern for the functionality it needs to offer and not also be responsible for playing a role in implementing the framework we need our code to run in.

In this way IoC can be seen as playing a role in ensuring we follow multiple elements of the SOLID principles, not only dependency inversion but also single responsibility and the open close principle. 

Too often we can simple show developers a piece of code and simply tell them this is good code.

But this encouragement to simply repeat what you've been shown without appreciation for why the code is good will inevitability lead to the subtitles of the code being missed.

It is never the case in software engineering that any particular pattern can be endlessly applied, there are always other factors to consider and different situations to recognise.

The way to enable developers to appreciate this is to teach them the principles that are used to define good code, which patterns to use are a natural consequence of understanding these principles. 

Sunday, 21 August 2016

Integrating with Users



Somewhere along the line, to a greater or lesser extent, all software is integrated into some kind of front end to allow users to interact with it and benefit from the value it can provide.
Too often this value is masked under a shroud of a confusing UI, causing users to fail to see this value because of what they perceive as a bad experience using the software.
It isn't enough to create a well crafted, well engineered piece of software without also putting as much effort into how the final piece of integration will be achieved, integration with your users.
Don't Astonish
They're certain noises that you don't want users to make when they are using your software. Some of these are obvious, you don't want shouting, screaming or swearing to be heard, but we also don't want any kind of puzzlement or astonishment.
Users do have to go on a journey of discovery when using any new piece of software, they need to learn what its capabilities are and what value they can bring. But users should never need to be trained to use a piece of software, this process of discovery should be one of self discovery.
If you find yourself having to construct tutorials, FAQs and help sections to explain to users how to operate your software then your preventing them from going on this journey.
The majority of your users will not be prepared to put this level of effort into learning how to utilise what you've built, your hard work in developing features will simply never be discovered.
Instead we should simplify wherever we can and let users stumble across the value we can deliver without expending much effort of grey matter.  
Don't Reveal Detail
Writing software can be a difficult and complicated matter, sometimes we are faced with solving challenges related to systems integration, legacy code bases or sub optimal implementations.
All of these problems must exist in the shadows, users should never be expected to be part of the solution.
These complications of implementation shouldn't be allowed to affect user experience, if a feature cannot be implemented to provide an acceptable user journey then we should question whether it represents any value to the user.
Users can be very fickle and don't forgive valiant attempts by developers to implement a feature despite the technical challenges if it doesn't sit well with how they want to use the software.  
Don't Find a 3rd Way
Sometime there is what is correct and there is what people actually want or understand.
Users like familiarity with what they have seen and used before, it may be that what they are familiar with is in some way flawed.
Just about every platform that you will use to deliver your software to users will have these conventions and perceived wisdoms.
I would never want to discourage anyone from trying to innovate but to go against these conventions of how people expect to use software on a platform can be fraught with danger.
Even if you think your new way is an improvement in order for users to not be put off by the unfamiliarity of what your presenting to them it needs to be a significant improvement.
Be cautious when innovating with new ideas for user experience, users can all too quickly dismiss something new it it doesn't match their expectations based on what has gone before.
Users are often difficult things to predict and we can be left frustrated when something that we've put a lot of time into and believe is dismissed or misunderstood.
But ultimately the user is always right even if for no other reason then you can't force people to use your software.
As with most aspects of engineering simplicity should always be something to strive for.
We may inevitably have a certain amount of complication happening under the hood but our user experience should be an effective shield from the user ever being aware of any of this, and instead be impressed by the apparent magic we are able to produce. 

Sunday, 7 August 2016

Don't Write More Code



As strange as it may seem we aren't in the software production business, we are in the solution delivery business.
What I mean by this is we shouldn't measure our effectiveness by the amount of code we produce but by the number of solutions we deliver.
We should actually consider code a necessary evil in delivering these solutions with it counting more as a liability than an asset.
Costs of Production
If viewed as a commodity code is pretty costly to produce, developers are highly skilled well paid people and good code can take time to engineer.
If we define an implementation that requires a lot of code to be written we are increasing our costs and putting our deadlines at risk.
We need to make sure that developers time is spent on writing the code that provides the features we really need, not re-inventing the wheel or falling into the not invented here trap.
The essence of this approach is re-use, whether this be utilising 3rd party code that does what you need or by recognising when you write code of your own that what you're writing will be the answer to a similar problem in the future, effectively reducing the time to provide the solution the next time round.
Whenever you find one of these re-use potentials your reducing your costs and making it more likely that you'll hit your deadline. 
Production Defects
Whenever we write software we are usually writing defects at the same time.
While I wouldn't suggest that its impossible to produce a small piece of code riddled with bugs or a large piece of perfectly bug free code, there is undoubtedly a relationship between the amount of code we write and the number of defects we create.
This is not only a problem because we don't want to ship bugs but also we create a vicious circle of even more developer time, which we've already established as expensive, being spent on fixing the issues. 
Re-use once again is our friend, by re-using code, especially well tested code, we can ensure that we build our software on strong well understood and proven foundations, reducing the amount of new code that is likely to cause defects.
Maintenance Not Guaranteed
Code can rot if not maintained, this can be because of constant change to deliver new features, hacks or patches being applied or not being kept updated with changes elsewhere.
The more code you have the more rot your going to see, and once again this brings us back to the time and cost of having to maintain and fix that code.
Yet again re-use can help us, by ensuring we are using the same solution whenever we encounter the same problem we reduce the amount of code that needs to be maintained.
If we discover a problem in a library or package we can fix it once and feel the benefit in every other code base without expending any more effort or cost.
Its very easy to fall into a trap of engaging in engineering software for purely technical reasons.
We are enthusiastic about what we do and gain pleasure from doing it, but our industry is not about production lines. We aren't employed because of our ability to produce code, we are employed because of our skill in utilising software to achieve business outcomes.
There doesn't have to be a relationship between the amount of code we produce and the number of outcomes we achieve.
Think of software engineering as a constant process of refinement rather than as a means to bring more code into the world. 

Sunday, 24 July 2016

Battling Against Time




When developing software there are many pressures that play a role in the final outcome.
The biggest of these pressures by far is time, anyone who has ever been involved in a software project will recognise that initial inward drawing of breath when the release date is first vocalised.
I'm not about to attempt to argue that these deadlines shouldn't exist, that simply isn't the real world. Product needs to be delivered to end users and this cannot be after their need has passed or after someone else has already provided value.
What I am going to argue is that these deadlines need to be based on input from the whole team and be based on not just delivering software on time but delivering value.
Viable Product
A fundamental part of an Agile approach is Minimum Viable Product (MVP), unfortunately it is also greatly misunderstood.
MVP is not simply a measurement of scope for a release, it is instead a measure of value that will delivered to users.
Their are many factors that go into deciding what constitutes an MVP, time to implement is not one of these factors.
Viability is not a sliding scale, software can not be somewhat viable it either can deliver a use case or it can't, and that use case is either valuable to users or it isn't.
Of course we can iterate, we can improve and we can deliver more, but this brings us to the minimalist approach that should be taken to MVP.
Some think that MVP only ever serves to push out deadlines, the goal of MVP is actually to deliver earlier by not concentrating on polish but identifying how a core need of users can be addressed with the minimum amount of implementation. Refinement and improvement come at an equally optimised pace by defining the next MVP and the next.
Making Effort
Discussions about deadlines often take place from a very entrenched position, I need this and I need it by then.
As we have discussed proper application of MVP can often lead to a realisation that maybe you don't need all those things, but what if you can't reduce scope any further?
Fundamentally a project only has two variables, scope and time, to fix both means the only other levers to be pulled are effort and quality.
A lesson that continues to be a difficult pill to swallow is that its very difficult to create effort and that the majority of the effective ways that this can be achieved are related to a teams structure and not just to the number of members.
Software is a creative process not just a manufacturing process, would a novel be written faster by more authors and maintain its narrative? Would a symphony be composed faster with more maestros and hold its tune?
So we are left with varying quality, the sad thing about this approach is that it often does work in the short term by bank rolling the release on technical debt. But with equal certainty it will also be the downfall of future releases when the interest on that debt has to be paid.
Release Early, Release Often
So I said I wasn't going to argue against deadlines, some reading this may think thats exactly what I've done but actually what I'm proposing is that we introduce more deadlines.
Rather than viewing the Agile process as a brake on driving towards deadlines we should see it as providing the acceleration that means we knock them off earlier then we thought possible.
An Agile approach means we can chip away at a mountain of scope, we may not be able to see the summit but we can establish the base camps that will be the factor in a successful ascent.
Rather than concentrating on the final release that will be the fruition of our grand plan identify the layers of value that means we can deliver something to our users to get them on board and help them keep the faith.
This means breaking away from waterfall thinking and embracing the iterative nature of Agile. 
Letting go of the illusion of control that anything can be made to happen in any timeframe, instead making the most of what the teams is capable of by identifying whats the next best thing we could deliver to incrementally deliver value.

Sunday, 17 July 2016

Arguments Against Agile



Whenever any philosophy or school of thought gains traction and reaches a critical mass of adopters a dissenting voice will start to develop against the new perceived wisdom.
This voice should never be ignored as while sometimes this can be simply a need to differentiate sometimes the points being made are genuine problems that need to be addressed.
Agile also has this opposition where some question its effectiveness.
I would never presume to say that Agile has all the answers but I do believe its as good a theory as we have yet devised. 
I also believe that most of the arguments put forward against are related to the flawed implementation of Scrum as opposed to criticism of an Agile mind set.
Too Many Meetings
A core principle of agile is face to face communication, this communication does not have to be regimented with an agenda, a duration or a fixed meeting place.
Simply put, the point Agile is making is that you'd be surprised how many problems get solved when those involved just talk to each other whether that be at their desks, at the coffee machine or over lunch.
A daily catch up with the team to ensure people are progressing and fortnightly opportunities to review progress and plan the next iteration do not seem onerous.
If you find yourself in an unending cycle of meetings, often about the same subjects, then you should question why there is so much uncertainty in the work your team are trying to get through or why there is so much disagreement on the way forward.
These problems are unlikely to be related to Agile, instead they are likely to indicate a lack of preparation, direction and vision.
No Commitment to Deadlines
A common frustration with Agile is an apparent inability to commit to a deadline.
Actually Agile teams commit to deadlines on a regular basis, every other week the team will commit to a certain amount of work being achieved in the next two weeks.
The team will also regularly discuss how they can improve there operating process so that they can commit to even more work in the next iteration.
Agile teams have a certain budget represented by their velocity, and this allows a certain scope of work to be completed in a certain time frame.
Agile is simply re-iterating the maths of this situation, that you cant fix time and scope because that involves you creating more effort. If you base your plan on the team creating more and more effort you will miss your deadline because thats not how teams or people can work sustainably.
Instead a deadline needs to be a compromise between timescale, scope and what the team think they can achieve, its possible for everyone to win.
Rigid Process
Some will say that they don't feel they can achieve as much with all the red tape and bureaucracy that Agile places on them.
Its critical in this situation to draw a distinction between Agile and the system we use to try and achieve.
Agile is not a system, a framework or a structure. Agile is a mind set and a philosophy.
Agile is nothing more than the principles laid out in the manifesto listing certain aspects of software development and team interaction that should be encouraged.
Scrum, and others like it, are attempts to organise teams to promote these principles. What they shouldn't become are rules and procedure that hang around the teams neck.
There is no one Agile to rule them all, each team needs to find their own way the only things they need to believe in and follow are listed in the manifesto the rest is implementation detail that while important is not the aim of the game.
Feel free to find your own way, adopting an Agile mindset will ensure you take the right path, even if your path is different from others you'll still be on track to reach your destination. 

Sunday, 10 July 2016

I've Got Something For That



Much like when someone disappears into a shed and emerges with the perfect tool for a particular job developers can often be heard saying "I've written something a bit like that before".
As I've said many times a good developer is a lazy developer, re-using what you've previously written should bring a smile to every developers face.
But how can we increase the chances that these opportunities will present themselves, what is it that makes code re-usable?
Simplicity Over Configuration
An easy trap to fall into is to take a piece of code thats fairly close to what you want and makes the bits that don't quite fit configurable.
While in a lot of cases this will yield a nice solution care must be taken to ensure that this won't run out of control. An object with a vast number of possible configurations can be difficult to get to grips with, what you need it to do might be in there somewhere but your eye isn't drawn to it.
This is more likely to happen when your trying to re-use code in a large block where the desired functionality is always going to be custom based on the context its being used in.
The answer to this is composition, by breaking this large code block down into small, simple, easy to understand building blocks you allow for different configurations of the functionality by allowing the developer to build the functionality that makes sense for them while taking advantage of the effort you've put in to taking care of the lower level details.
Layering the Pie
In a similar vain composition is easier to achieve when your code has a clear hierarchy taking us from low level detail all the way up to chunks of functionality that a user would recognise.
These functional blocks will very often be able to traced back to the same lower level detail.
By working out at what level certain pieces of code should exist you can avoid duplication and move towards a toolbox of code.
Obvious examples of this would be a data access layer or a networking layer, but even here recognising that there is a common pattern to how network requests are built, queries are constructed or results are interpreted can help the extract, abstract and re-use cycle that we are aiming for. 
Guarantee Included
Identifying that a piece of code might suit your needs is only the first step, it would be nice to also have some evidence that it does do what it claims.
This could come by reviewing the source code but it wouldn't it be better to have the code come with a guarantee of its effectiveness.
This evidence should come in the form of unit testing, those test results should not only demonstrate that the code works but also act as documentation for how the code is expected to be used.
This testing as documentation approach will help re-enforce that this is the piece of code that you've been looking for, this will solve your problem and enable you to move forward.
All this means that testability should be seen as synonymous with reusability, along with the guarantee and documentation elements the qualities of code that make it testability such as loose coupling will also make the possibility of reuse much higher.
While in many aspects of engineering becoming obsessed with a single element of the design may not be optimal striving to make something reusable will naturally lead to many other qualities.
Sometime code will be custom to a particular situation but a divide and conquer approach and thinking about how code will interact can help minimise these situations and help a developer be a a lazy developer. 

Sunday, 3 July 2016

Tech Debt Accounting



Technical debt is incurred whenever a sub-optimal solution to a problem is chosen because of some other pressure aside from technical merit.
Technical debt is only technical debt if you knowingly choose that sub-optimal solution, anything else should come under the heading of mistake, poor judgement or idiocy.
So if we knowingly incur this debt why don't we just always do the right thing, I would love to tell you that its possible to do that but the reality of working in commercial software development makes it difficult to truly put technical concerns before all others.
We Need to Ship
The reason we write software is to ship it and there are many more inputs into the decision on when or if to do that aside from the purely technical.
I would like to be able to give you advice to never allow your code base to be affected by these outside pressures but it just wanted be realistic.
Shipping software is important, it reenforces our link with our users and reassures them that we are working on ways to improve the product.
Its a fine line between making sure software is properly implemented and meeting a schedule of releases and because of this we will occasionally stray either side of it.
We may occasionally ship late and we may sometimes ship something we aren't 100% happy with it, if we accept this is the reality then we need a strategy for dealing with the consequences. 
Auditing Your Finances
So if we assume that at any time we are carrying a certain amount of debt how can we ensure that it is effectively managed.
The first step is to record it, the chances are all developers working on a code base know the areas that need work or that cause problems when dealing with change.
But this isn't enough, too often when developers talk about this stuff it doesn't register with others and this is mainly because they can't see the impact.
Instead have a central register of tech debt that briefly explains the nature of the debt and the impact it has. The next time in a planning meeting eyebrows are raised as to timescales or complexity being able to point at an entry in the register and explain this is why is invaluable.
Secondly every time a bug or performance issue is encountered that is either directly caused by tech debt or exacerbated by it assigning the developer time taken up in finding a resolution and the users affected will highlight that everyone is suffering from the debt.
Visualising the impact of the technical debt like this helps highlight to non-techies that there is value in this kind of maintenance and continual improvement, stability and performance can be a feature.
Now or Later
The biggest piece of education you can conduct around technical debt is that its currency is time.
Time saved by choosing the sub-optimal solution is always paid back in either longer timescales for new features or more time spent on irritating bugs or failures further down the line.
This doesn't always mean we are doomed to either ship later or spend time fixing our code base, we instead need to understand that tech debt cannot be ignored. That it isn't related to people not doing their jobs properly, instead we can all work together to ensure we instigate a cycle of shipping and optimising and shipping.
Stability and performance are often overlooked features but users do not forgive lapses in either of those two things for sake of a shiny new feature. Its not always easy but it is possible to establish a balance between the technical merit and the need to ship everybody just needs to be aware of the nature of debt.