DEV Community

Cover image for The One Thing Every Developer Should Know
Brock Chestwall
Brock Chestwall

Posted on

The One Thing Every Developer Should Know

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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

ConsoleOutputService.js

class ConsoleOutputService {
  print(content) {
    console.log('File Content:', content);
  }
}

module.exports = ConsoleOutputService;
Enter fullscreen mode Exit fullscreen mode

FileReadingFactory.js

const FileReaderService = require('../services/FileReaderService');

class FileReadingFactory {
  createFileReader(filePath) {
    return new FileReaderService(filePath);
  }
}

module.exports = FileReadingFactory;
Enter fullscreen mode Exit fullscreen mode

OutputFactory.js

const ConsoleOutputService = require('../services/ConsoleOutputService');

class OutputFactory {
  createOutputService() {
    return new ConsoleOutputService();
  }
}

module.exports = OutputFactory;
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
jonrandy profile image
Jon Randy 🎖️

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.

Collapse
 
jonrandy profile image
Jon Randy 🎖️

INot sure

Collapse
 
kingtouan1703 profile image
Dinh Anh Tuan

Agree!!!

Collapse
 
brock_chestwall profile image
Brock Chestwall

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?

Collapse
 
dagnelies profile image
Arnaud Dagnelies • Edited

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. 😅

Collapse
 
simongreennet profile image
Simon Green

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.

Collapse
 
brock_chestwall profile image
Brock Chestwall

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:

/*
  Purpose:
  This file defines a class named OutputFactory, responsible for creating an instance of an output service.

  Behavior:
  - The class contains a method createOutputService, which instantiates and returns a new ConsoleOutputService.
  - The ConsoleOutputService is assumed to be a service responsible for handling console output operations.

  Motivation:
  - The OutputFactory pattern is used to encapsulate the creation of output service instances, providing a centralized point for instantiation.
  - The use of a factory allows for flexibility in changing or extending the type of output service without modifying the client code directly.
  - This modular approach supports better code organization and maintainability by separating the concerns of output service creation from the rest of the application.

  Note: The actual behavior of ConsoleOutputService, as well as its dependencies, is not provided in this code snippet.
*/
const ConsoleOutputService = require('../services/ConsoleOutputService');

class OutputFactory {
  createOutputService() {
    return new ConsoleOutputService();
  }
}

module.exports = OutputFactory;
Enter fullscreen mode Exit fullscreen mode

Without context, the code will be hard to reason about indeed!

Collapse
 
brock_chestwall profile image
Brock Chestwall

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!

Collapse
 
joolsmcfly profile image
Julien Dephix

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.

const fs = require('fs');

try {
  const fileContent = fs.readFileSync( 'example.txt', 'utf8');

  console.log('File Content:', fileContent);
} catch (error) {
  console.error('Error reading the file:', error.message);
}
Enter fullscreen mode Exit fullscreen mode

Next I'd look at all those, erm, ONE line of code and wonder:

do I need an abstraction layer for a self-explanatory method, namely fs.readFileSync?

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?

Collapse
 
brock_chestwall profile image
Brock Chestwall

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.

Collapse
 
joolsmcfly profile image
Julien Dephix

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?

const fs = require('fs');

try {
  const fileContent = fs.readFileSync( 'example.txt', 'utf8');

  console.log('File Content:', fileContent);
} catch (error) {
  console.error('Error reading the file:', error.message);
}
Enter fullscreen mode Exit fullscreen mode

Because translated to plain English it gives us:

grab a book
read it
Enter fullscreen mode Exit fullscreen mode

Your implementation:

see a book
ask neighbor for a book grabber
use book grabber to grab a book
ask neighbor for a book reader
use the book reader
Enter fullscreen mode Exit fullscreen mode
Collapse
 
bwca profile image
Volodymyr Yepishev • Edited

The true solution lacks Typescript, interfaces, abstract classes, method overrides, package.json and over 9000 gigs of node_modules :)

Collapse
 
kurealnum profile image
Oscar

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"

Collapse
 
michalkuncio profile image
Michał Kuncio

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...

Collapse
 
tandrieu profile image
Thibaut Andrieu

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 !

Collapse
 
brock_chestwall profile image
Brock Chestwall

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!

Collapse
 
m0n0x41d profile image
Ivan Zakutnii

The one two things that every developer should know are type theory and how to code without states.

Image description

Collapse
 
brock_chestwall profile image
Brock Chestwall

Interesting... could you elaborate? Is it possible to structure programs without dynamic OOP?

Collapse
 
m0n0x41d profile image
Ivan Zakutnii

Your joke even better than my ;D

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him) • Edited

This post is just a joke, right? Right!?!

Collapse
 
totalian profile image
totalian

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.

Collapse
 
brock_chestwall profile image
Brock Chestwall

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

Collapse
 
syeo66 profile image
Red Ochsenbein (he/him)

And don't forget to include Kafka. It really calls for it.