Introduction
Let's get to it: simplicity in coding. Imagine your code—clean, clear, easy to understand. That's simplicity. Whether you're a pro or just starting, simplicity is your companion.
It turns your code from a puzzle into a straightforward conversation. Stick with me as we explore why simplicity isn't just a good idea; it's the fundamental thing every coder should embrace. Keep it straightforward, and let's enhance your coding journey together.
Making unsimple code
Let's start with an example:
// THIS CODE IS HORRIBLE
const fs = require('fs');
try {
// Specify the file path
const filePath = 'example.txt';
// Read the file content synchronously
const fileContent = fs.readFileSync(filePath, 'utf8');
// Print the content to the console
console.log('File Content:', fileContent);
} catch (error) {
// Handle any errors that might occur during file reading
console.error('Error reading the file:', error.message);
}
This code is not simple. It's hard to follow and poorly organized. Where's the DRY (Don't repeat yourself!) and separation of concerns? Here's how a senior developer would code this in a fast moving tech job.
The true solution
your-project-root
│
├── src
│ ├── services
│ │ ├── FileReaderService.js
│ │ └── ConsoleOutputService.js
│ │
│ ├── factories
│ │ ├── FileReadingFactory.js
│ │ └── OutputFactory.js
│ │
│ └── index.js
│
└── example.txt
This is starting to look better! We can clearly see at a glance of the file system what our program is doing. More importantly, we can see that our concerns are SEPARATE.
FileReaderService.js
const fs = require('fs');
class FileReaderService {
constructor(filePath) {
this.filePath = filePath;
}
readFile() {
try {
return fs.readFileSync(this.filePath, 'utf8');
} catch (error) {
throw new Error(`Error reading the file: ${error.message}`);
}
}
}
module.exports = FileReaderService;
ConsoleOutputService.js
class ConsoleOutputService {
print(content) {
console.log('File Content:', content);
}
}
module.exports = ConsoleOutputService;
FileReadingFactory.js
const FileReaderService = require('../services/FileReaderService');
class FileReadingFactory {
createFileReader(filePath) {
return new FileReaderService(filePath);
}
}
module.exports = FileReadingFactory;
OutputFactory.js
const ConsoleOutputService = require('../services/ConsoleOutputService');
class OutputFactory {
createOutputService() {
return new ConsoleOutputService();
}
}
module.exports = OutputFactory;
Making the simple program
Now that our dirty program is separated into smaller, more reusable parts- we can write an elegant solution for our business needs:
const FileReadingFactory = require('./factories/FileReadingFactory');
const OutputFactory = require('./factories/OutputFactory');
try {
const fileReadingFactory = new FileReadingFactory();
const outputFactory = new OutputFactory();
const fileReader = fileReadingFactory.createFileReader('../example.txt');
const outputService = outputFactory.createOutputService();
const fileContent = fileReader.readFile();
outputService.print(fileContent);
} catch (error) {
console.error(error.message);
}
The new,simple version introduces an elegant architecture, leveraging classes, factories, and services, creating a beautiful structure.
For senior developers aspiring to excel, embracing this level of abstraction promotes scalability, maintainability, and an overall superior coding aesthetic.
It's not just about code; it's about crafting a masterpiece that elevates the art of development. #CodeElegance #SeniorDevWisdom 🚀✨
Top comments (29)
Sorry, but this is a pretty poor example. The 'unsimple' code is perfectly easy to follow, and the 'simple' code is major overkill for such a basic task, and following it requires opening 5 files.
Embracing this level of abstraction to make such a simple program is almost certainly the wrong thing to do IMO - and knowing this should be another thing that comes with experience.
Doing it might be a wise move if the 'task' formed part of a larger program where many of the component parts could be re-used nicely, but here it makes no sense at all. It's architecture for architecture's sake.
Agree!!!
While I don't understand you argument, I hope we can learn to change your perspective on the issue. Sure, "abstraction" is the way to go, but shouldn't we opt for more general programs?
I had a good laugh. You transformed a few lines of simple code in a bunch of useless abstractions over several files. Congrats!
This could lead to a challenge of who writes the most contrived abstractions for the simplest tasks! Let's make a builder for the factory and throw a singleton in the mix! 😂
I seriously wonder if the post was a piece of sarcastic/ironic humor or not. 😅
The original code had five comments. The new code had none. That makes it instantly much harder for the next developer who looks at this code to figure out what is going on. You may know the five files are, the next person doesn't.
You're right, I usually like to add an introduction to each file, but for the sake of brevity for the post– I omitted the comment headers here.
Here's how I would make the revised code structure clearer:
Without context, the code will be hard to reason about indeed!
Haha, I'm glad you liked the post!
I think "contrived abstractions" is a little harsh for the first example. There are actually more abstractions in the revised version– an "abstraction" is really what's missing to make this code scalable in a serverless context!
Not sure what to think of this post. If it were published on April 1st then I'd have hi fived you.
Now, let's assume you're serious and really thought this post through.
Comments in the "horrible" code are useless as they add no value so removing them would be my first "refactoring". I'd also inline
filePath
as it's only used once.Next I'd look at all those, erm, ONE line of code and wonder:
Answer is no so I'd leave the code as is and move on to another task.
The original idea of your post is good: simplicity is key. Your implementation is far from being simple.
Don't use a tank to kill a fly.
Again, not sure if this is mocking people who overkill?
IMHO, every code problem appears to be a "fly" until you need the "tank". Better be precautionary than regretful in the long run! But thank you for sharing this alternative angle, it helps to have a free discussion in these forums.
Sure, we should be cautious when writing code but you rarely find yourself in need of a tank overnight. When/if you do it's more often than not at the database level.
Now, back to your example (cos I really am not sure whether you're taking the piss or not), do you really think this code is, and I quote you, poorly organized?
Because translated to plain English it gives us:
Your implementation:
The true solution lacks Typescript, interfaces, abstract classes, method overrides, package.json and over 9000 gigs of node_modules :)
Might be satire, I'm not sure. Either way, I just follow the rule of "abstract when you need to, don't abstract when you don't need to".
And this is definitely a case of "don't abstract when you don't need to"
Your example is basically what senior would not do, rather"clever" mid by creating tons of useless abstractions and regret it in a few months/years. And those factories... Why...
There are still hard-coded values in your code (example.txt), and your module structure is tightly coupled with file system (../services/FileReaderService).
You should put an abstraction over this. Put your class in packages and use a "PackageDiscovery" pattern so that you would just have to write the package name. If it is not available locally, download it automatically using system's package manager. Any Senior developer know this !
Ah, yes! At my business we call this the "Chance Pattern"– it's an effective means of adding more value to the existing codebase.
I'll consider your proposal with great haste– thank you!
The
onetwo things that every developer should know are type theory and how to code without states.Interesting... could you elaborate? Is it possible to structure programs without dynamic OOP?
Your joke even better than my ;D
This post is just a joke, right? Right!?!
This is a pretty good start at simplifying the code. However, as a true senior dev - I would take this a step further implement a basic microservice architecture. As the program is not too complex the code could probably be refactored into only a few hundred independent services.
Finally, I would somehow manage to include Docker and Kubernetes in my solution. As every senior dev knows, your code is not truly simple if you can't somehow incorporate Docker containers.
I hope to follow up with some nuggets of hosting wisdom. Kubernetes (or K8s for short) can get a bad rap– but ultimately it's the only way to grasp the helm of virtualization.
Maybe a future article can help scratch your curiosity itch
And don't forget to include Kafka. It really calls for it.