Usually when I talk to other developers about my Proactive Lazy Developer concept, a debate starts: putting extra time and effort to build a highly abstract system so that the developer can be lazy later is a violation of YAGNI!
Here I will try to argue that YAGNI fits well with the art of being Proactively Lazy and argue that the SOLID ,Object Oriented and YAGNI principles are to be used together, but with different priorities.
What Is SOLID, OOP AND YAGNI!?!?
This post is assuming that you have some familiarity with SOLID Principles and YAGNI. If so, you can skip this section. However, I’ll do a quick overview:
SOLID Principles (Wiki)
SOLID is an acronym that stands for the five basic principle of object-oriented programming introduced by Michael Feathers:
- Single responsibility principle
- Open/closed principle
- Liskov substitution principle
- Interface segregation principle
- Dependency inversion principle
The intention is that if a developer follows these principles, then they will be able to create a highly maintainable and extensible software system.
OOP (Object-Oriented Programming) (Wiki)
OOP is a programming language model organized around objects rather than “actions” and data rather than logic.
YAGNI (Wiki)
YAGNI is an acronym that stands for “You aren’t gonna need it.” It’s a principle that states that developers should not add anything to a system that is not deemed necessary. This principle helps prevent the developer from putting in too
The Conflict
XP co-founder Ron Jeffries has been quoted about YAGNI: “Always implement things when you actually need them, never when you just foresee that you need them.” So, if a developer is not supposed to implement things only when needed, how does a developer apply the SOLID/OOPS Principles without being in conflict with YAGNI?
How can I be a proactive lazy developer and still follow both sets of principles?
Proactive Lazy Solution
What I propose is this:
SOLID/OOPS principles should always have a higher priority than YAGNI
SOLID and OOPS are principles to help build maintainable and extensible systems.
YAGNI is a principle to help build maintainable systems, but doesn’t help with extensibility.
Remember, we want to make life as easy as possible later on so we can be lazy. If our system isn’t extensible, then we can’t be lazy in the future.
WebAPI Example
Let’s say that we need a WebAPI to create and get an article in a MongoDB database. We also need it to log the behavior for audit purposes.
YAGNI Example
Below is an example that applies YAGNI only to a NodeJs WebAPI:
var express = require('express');
var app = express();
var mongoose = require('mongoose');
var loggly = require('loggly').createClient({settings: process.env.logglySettings});
app.get('/api/articles/:id', function(req, res) {
loggly.log("Retrieving An Article");
var articleId = req.params.id;
mongoose.connect('mongodb://localhost/test');
var ArticleModel = mongoose.model('Article', { name: String });
ArticleModel.findOne({ _id: articleId })
.exec(function(err, article) {
res.json(article);
});
});
app.post('/api/articles', function(req, res) {
loggly.log("Creating An Article");
var articleId = req.params.id;
mongoose.connect('mongodb://localhost/test');
var ArticleModel = mongoose.model('Article', { name: String });
var article = new ArticleModel(res.body);
article.save(function(err, updatedArticle) {
res.json(updatedArticle);
});
});
YAGNI as applied to it’s structure:
- Everything is in one file.
- Monolithic methods: one method for each function needed.
YAGNI as applied to it’s function:
- It gets an article
- It creates an article
- It logs the two functions desired.
This WebAPI was very quick to create. It does only what was needed (create and get an article) and nothing else. Copy and paste it into a NodeJs/ExpressJs project and it’ll just work. Straight to live I say!!!
However, this example violates most SOLID principles:
- The code has too many responsibilities: providing an interface and updating a database.
- It is dealing with strict implementations of logging vs an abstract.
SOLID Before YAGNI (The Proactive Lazy Way)
Now let us set a higher priority on the SOLID principles.
<strong>//Logger.js</strong>
var loggly = require('loggly').createClient({ settings: process.env.logglySettings });
class Logger {
log(message) {
loggly.log("Retrieving An Article");
}
}
<strong>//ArticleRepo.js</strong>
var mongoose = require('mongoose');
var logger = new(require('./Logger'))();
class ArticleRepo {
get(articleId) {
logger.log("Retrieving Article From MongoDB");
return new Promise(function(resolve, reject) {
var ArticleModel = mongoose.model('Article', { name: String });
return ArticleModel.findOne({ _id: articleId })
.exec(function(err, article) {
return resolve(article);
});
});
}
insert(article) {
logger.log("Inserting Article From MongoDB");
return new Promise(function(resolve, reject) {
var ArticleModel = mongoose.model('Article', { name: String });
var insert = new ArticleModel(article);
insert.save(function(err, newArticle) {
return resolve(newArticle);
});
});
}
}
<strong>//app.js</strong>
var express = require('express');
var app = express();
var repo = new(require('./ArticleRepo'))();
var logger = new(require('./Logger'))();
app.get('/api/articles/:id', function(req, res) {
logger.log("Retrieve Article Requested");
repo.get(req.params.id)
.then(function(article) {
res.json(article);
});
});
app.post('/api/articles', function(req, res) {
var articleId = req.params.id;
repo.insert(res.body).then(function(addedArticle) {
res.json(addedArticle);
});
});
SOLID application:
- Single responsibility applied for the interface, logging, and persisting data to database.
- Dependency Injection principle used by classes using abstract logging, and repo classes.
YAGNI as applied to it’s function:
- It gets an article
- It creates an article
- It logs the two functions desired.
Yes, there are YAGNI violations. But, they are there because of the priority of satisfying the SOLID and Object oriented principles.
How Does This Make Us Proactively Lazy?
Now we have some code that is better suited for proactive lazy development. Now you may ask: how does this code help with being proactively lazy?
Reusable Code
There are 2 pieces of code that was created: Logger and ArticleRepo. These two classes have a distinct responsibilities: logging a message and persisting article data. Now that we have created them for this WebAPI, we can now used them any where else in the code that will need either logging and/or article data persistence. The best part, we won’t have to keep retesting that code since we already made it work for the articles WebAPI. That will make use very lazy later on!
Extensible Code
Since we have abstracted out the Logger and ArticleRepo, we can now extend them to do more if needed. Let’s say we need to fully qualify a logo that is stored in the article. We can easily extend the ArticleRepo to add the logic to fully qualify the logo path. Other parts of the system that is using the ArticleRepo will still work as expected and we can use the extended version to fully qualify the logo url were needed. Now we can be lazy with regards to extending features in our system.
Testable Code
To make our code better maintainable, the abstraction of the Logger and ArticleRepo now makes our code better testable. Having automated tests helps us lazy developers push out more code faster since we can rely on automated tests to tell us if we messed something up.
We can unit test the WebAPI interface to make sure it will always call the correct repo methods. We could also write integration tests with the ArticleRepo to make sure it will always persist and query data as expected. Once we get these tests in place, we can be very lazy in the future since we know this code will always work as expected.
Overview
Two important principles, YAGNI and SOLID/OOP, are needed to create great maintainable and extensible systems. However, when you get into a situation where SOLID/OOP and YAGNI are in conflict, SOLID/OOP should aways take priority.