Techniques and Practices You Can Only Learn from Experience as a Software Developer <Part 1/>

Damilare Protocol Arikeusola
7 min readSep 9, 2023

--

Becoming a proficient software developer goes beyond the mastery of programming languages and tools or the creation of a handful of side projects. It’s a journey that involves acquiring practical experience over time and grasping the nuances of software development through real-world encounters. As a beginner in the world of software development, regardless of your innate intelligence or rapid learning abilities, some practices and techniques can only be truly absorbed through hands-on experience with actual projects.

In my journey as a software developer, I’ve navigated the complexities of various projects, each presenting its unique set of challenges. Along the way, I’ve gained insights into valuable techniques and practices that transcend the confines of any specific technology or programming language.

In this article, we’ll embark on a deep dive into these techniques and practices, showing their relevance, and application. To ensure a comprehensive grasp of these concepts, I’ll explain them through high-level explanations, complemented by practical PHP (Laravel) code examples. So, whether you’re a budding developer or a seasoned pro, these are very useful techniques and practices for you.

1. Automated Testing and Incorporate Testing into Your CI/CD Pipeline

Automated testing is a practice that some developers initially find optional, often because they haven’t personally encountered situations where testing could have saved them days of troubleshooting and iterative fixes. Picture this scenario: You’ve developed a software application, and it’s time to deploy it. Soon after deployment, a user reports a bug that slipped through the cracks.

You dive back into the code, determined to address the issue swiftly. You pinpoint the problem and make the necessary fix, but here’s where the challenge arises. The fix you introduced unexpectedly triggers errors in other parts of the project, creating a cascade of unintended consequences. You find yourself stuck in a frustrating loop of troubleshooting, deploying, discovering new side effects, and repeating the process.

Sometimes, you may not even detect these side effects until after you’ve redeployed, leaving you in a perpetual cycle of debugging and backtracking. To make matters worse, there are instances when you must retest all your application’s features to ensure that nothing else has broken due to the recent changes.

Now, imagine a different scenario — one where automated testing is an integral part of your development process, and testing is seamlessly integrated into your Continuous Integration/Continuous Deployment (CI/CD) pipeline. Once you’ve personally encountered the challenges of bypassing automated testing and the CI/CD pipeline, you’ll realize that these practices transform from optional to imperative. If you need a more detailed article on automated testing best practices, I recommend Mastering Application Testing It is a good resource with different case studies in JavaScript

2. Use Enum for Status Codes and Table Columns

Enumerations provide self-explanatory and human-readable constants. It makes it clear which values are valid and eliminates the risk of inconsistent definitions throughout your application. See an example of Enum to declare Order status below:

enum OrderStatus {
case PENDING;
case PAID;
case DELIVERED;
}

In many real-world applications, you encounter scenarios where you must define a set of status codes e.g. pending, completed, success. Instead of creating a separate database table to store each status, we use Enum. using enums simplifies your code and ensures consistency. Implementing enums for status codes and other related functionality provides a clean and efficient way to manage predefined values without the overhead of a database table. You can easily understand the meaning of each status without querying a database or referring to documentation.

When defining status columns in a database, consider using enums instead of plain strings or integers. Enums provide clear and predefined options for status values, enhancing code clarity and robustness. Let’s take a look at a scenario to understand how to use an enum as opposed to using a database table for status codes.

Imagine a scenario where you are developing an inventory system and each product needs to have a status. Products can either be in-stock, out-of-stock, etc, First create an enum with all the possible product statuses.

Although we use product status as an example, this technique can be used across all status codes and related functionalities, it is not limited to product status.

<?php
// Define a ProductStatus enum (PHP 8.1+)
enum ProductStatus: string {
case IN_STOCK = "in-stock";
case OUT_OF_STOCK = "out-of-stock";

// define method to get enum values as an array ['in-stock', 'out-of-stock']
public static function values(): array
{
return array_map(fn(ProductStatus $status) => $status->value, self::cases());
}
}

Then create the product status column using enum as data type in the database product schema. Remember to set a default value.

// In your database schema (Laravel migration):
$table->enum('status', ProductStatus::values())
->default(ProductStatus::IN_STOCK->value);

And you can use it this way

// Set a product's status
$product->status = ProductStatus::OUT_OF_STOCK->value;

// Save the product to the database (assuming you're using an ORM like Eloquent)
$product->save();

Using an enum, we were able to set the values of the product status and assign the default value from the productStatus enum.

This approach accommodates dynamic addition of statuses, making it future-proof, regardless of the technology you use. You can easily use the status from the enum without trying to check the value for the status in the database, worrying about the uncertainty of whether there is a typographical error or even the possibility of using the wrong status before using it.

3. Use Queues for Time-Consuming Tasks Like Sending Emails

Queues are a powerful tool for offloading time-consuming tasks to background workers. This can encompass various scenarios not limited to sending emails, such as processing orders, generating reports, or performing calculations asynchronously.

Email communication is an integral part of modern web applications. However, sending emails synchronously, especially in high-traffic scenarios, can be resource-intensive and slow down your application, this is where email queues come to the rescue. Imagine a scenario where you have hundreds of users registering on your product simultaneously, and for each registration, you send a welcome email. Without email queues, this scenario could overwhelm your server, leading to performance degradation and potential email delivery issues.

// Queue email after registration
protected function registered(Request $request, $user)
{
// After registration, Push the SendWelcomeEmail job to the queue
Mail::queue(new SendWelcomeEmail($user));

return
}

For the detailed implementation of queuing email in Laravel, check the Laravel documentation on queuing email

Queues are not limited to just sending emails; they enable you to handle a wide range of tasks asynchronously. This practice enhances user experience, system performance, and scalability in real-world applications, regardless of the technology used.

4. Dynamic Configurations for Future Modifications

Early in my career, I discovered a practice that has proven invaluable, particularly for freelancers and developers working closely with clients. Imagine you’ve developed a robust application for a client, complete with essential configuration data such as phone numbers, email addresses, and office locations. The initial implementation involves hardcoding these details directly into the codebase, providing a functional solution.

However, fast forward a year, and your client needs to update their office address, phone number, or any other relevant data. To accommodate this seemingly simple change, they have to reach out to you, the developer, each time. It’s a process that introduces delays, and reliance on developers for minor updates, and can be cumbersome for both parties.

In scenarios like this, where clients or application users may need to modify configuration data without delving into the intricacies of code changes, adopting a dynamic configuration approach becomes vital. By storing configuration settings in a separate config file or a database, you enable easy adjustments, such as changing email addresses, thresholds, or behavior rules, without altering the application’s source code. This practice empowers users to adapt the software to their evolving needs without relying on developers for every configuration update.

Let’s take a look at an example of using dynamic configuration using a configuration file.

<?php

class ConfigLoader
{
private $config;

public function __construct($request)
{
$this->config = $request
}

public function get($key)
{
return $this->config[$key] ?? null;
}

public function set($key, $value)
{
$this->config[$key] = $value;
$this->saveConfigToFile();
}

private function saveConfigToFile()
{
$configFile = fopen('dynamic_config.php', 'w');
fwrite($configFile, '<?php' . PHP_EOL . 'return ' . var_export($this->config, true) . ';');
fclose($configFile);
}
}

// usage

// request sent from user interface
$post_request = [
'admin_email' => 'admin@example.com',
'phone_number' => 3383373636,
'app_name' => 'My App',
'office_address' => '36, off address, UK'
];

// Create an instance of the ConfigLoader
$configLoader = new ConfigLoader($post_request);

// Get a configuration value
$adminEmail = $configLoader->get('admin_email');
$officeAddress = $configLoader->get('office_address');

The above code snippet, which might need some modifications for completion and security purposes demonstrates a subtle implementation of dynamic configurations. consider taking the steps below for this approach.

  1. User Interface: Create a user-friendly interface that allows clients or users to send requests for adding or updating configuration data. This interface can include appropriate validation and access controls to ensure that only authorized users can make changes.
  2. Configuration Management Class: Develop a dedicated class or component that handles the manipulation and storage of configuration data securely. This class should be responsible for validating input, enforcing security measures, and ensuring that the changes adhere to predefined rules and constraints.
  3. Choice of Storage: While our example used a file for storing configuration data, you can choose a storage method that aligns with your application’s needs. This could be a database, a cloud-based storage solution, or a specialized configuration management tool. The key is to select a storage mechanism that suits your application’s scalability, performance, and security requirements.

The ultimate objective is to establish a robust and adaptable dynamic configuration system that empowers clients or users to make necessary changes while maintaining security and reliability without delving into the codebase. By following these practices, you can ensure that your application’s configuration remains flexible and responsive to evolving needs.

The techniques and practices explained in this article are demonstrated through real-world scenarios, to equip you with the skills needed to excel in software development. By embracing these principles, you can create robust, maintainable, and performant applications that stand up to the challenges of the real world, accommodate future modifications with ease, and empower users to configure software according to their evolving requirements.

Is this helpful? consider giving it a like and leaving a comment. Follow me, on Twitter, and connect on LinkedIn for more useful tips.

Happy coding! and stay tuned for Part 2.

--

--

Damilare Protocol Arikeusola

I am a software engineer, I like to help people and I enjoy desolving technical complexities