Strategies of Excellence: The Simplest Possible Environment

Table of Contents

Download the full PDF version of the article here.

Who doesn’t love those magical moments when a project begins to take shape, and new functionalities appear one after another, leaving us delighted with our programming masterpiece? We nurture every piece to make our software even more magnificent.

However, this is also the moment when the project becomes increasingly complex. For instance, with a web application, there comes a time when it supports logging in, transactions, database interactions, multilingual capabilities, and complex security rules. At this point, there’s usually some user event processing logic, often handled by an MVC framework. The system is indeed quite complex. Restarting the entire application can take tens of seconds, sometimes even minutes.

Every day spent working in such an environment makes us accustomed to all the complexity. Moreover, we become more familiar with it and feel at ease, enjoying all the well-known nuances. We settle into the routine.

As we continue developing the application, adding new functionality, such as a chart screen based on system data, we’re learning to work with a charting library for the first time. As we explore the skills related to creating charts, we begin to embed new solutions into this complex system, needing to test various options and understand how the component operates.

So, we experiment with different parameters. Before testing, we must properly configure the MVC framework, embed the chart component on a page or panel, and maybe add elements related to security and multilingualism. All of this just to learn how to use the component.

But we are just learning…

Just experimenting!

We don’t need all this overhead.

Suddenly, one parameter doesn’t work as desired, and the system won’t even start because of a hastily coded MVC controller, dealing with its errors like data access issues.

Finally, we succeed, returning to our component—only to restart the app, which takes quite a long time. Too long.

Is this entire environment necessary to test the component? The overhead is immense—you have to create all the elements the environment mandates to run the test fragment. In subjective assessment, the time needed to effectively test subsequent parameters gets extended by 10-50% due to the complexity of the system.

What if there was a simple web application environment at hand (sometimes provided alongside libraries as a blank application) used for testing new elements or simple functionality fragments? No overhead from other environment elements like security, multilingualism, or logging—just the simplest approach possible. Of course, once trials run successfully in the simple environment, we transfer the solution to the complex system we’re working on.

Maybe you don’t create web apps, but you’ll certainly find parallels within your field of expertise. The general approach remains the same across technologies.

This may seem obvious—it is obvious. Yet, this is why we often forget it, wasting time unnecessarily and straining our nerves by analyzing data layer exceptions while experimenting with a chart component. I’ve often witnessed (or participated in) such situations!

The essence of the above discussion boils down to a fundamental thought:

Work in the simplest possible environment

It’s easy to justify, thinking, “…but in my environment, nothing can be simplified! What I do can’t be moved to a simpler environment…” I assure you, Reader, that almost always, the environment can be simplified. The main question to answer is whether it’s truly worth it, for there are cases where creating a simplified environment is unprofitable. However, in most cases, it’s worth doing, especially since the developed solution can often be used for future functionalities, making the effort multiply worthwhile.

Below are some practical ways to simplify your environment. I’m sure they will inspire you, Reader, to work even more effectively and make the wonderful profession of programming even more enjoyable.

Create Simplified Work Contexts

The example mentioned initially embodies this principle.

When you want to test a new component or library, think about how you can test and understand its features most simply. Usually, the complex application you’re working on isn’t the best place for this.

If you’ve never used regular expressions and now need them, create a class with a main method (referring to Java here, although you can substitute any way to create a simple HelloWorld) and test all relevant cases there. You can substitute regular expressions for any function: functional objects from Jakarta-commons, PDF generation, data copying, SMTP server connections, and mailing, database interaction libraries, or statistical functions—the list goes on.

When working in a complex environment, such as building websites with an MVC framework, and wanting to test a new graphical component, it’s usually better to do so outside the project.

If testing a text field with a mask that enforces a data format, do it separately from the application to save time. Once you do this once, the second, third, and subsequent times become easier.

Test Units and Encapsulate Logic

If you’ve wondered about the usefulness of unit tests, they are fantastic tools for simplifying work environments. They replace the main method as a substitute and offer more complex test case possibilities. Additionally, using mock objects allows working with a minimal subset of business logic.

For example, if developing a method or function to prepare email content and send it, we don’t need to send it physically each time. Substitute a mock object (a simplified implementation for testing purposes) to avoid waiting for the email to be sent. However, this can be tested separately. You can test sending at the end when both preparing and sending work. Follow the principle:

Test, modify, or work on one thing at a time (this principle extends beyond programming!)

Note: Applying these principles won’t shield you from errors but will reduce situations where they arise. Imagine saving an hour daily by simplifying the environment (perhaps more), totaling five hours a week, 20 a month. What about over a year…?

Returning to tests—fully utilizing unit tests involves mastering a skill I call logic encapsulation. Developing software requires modular code to minimize dependencies (or coupling), allowing independent use of each part. Object-oriented design philosophy aims to achieve minimal interdependence.

However, both object-oriented and non-object-oriented systems can follow a broader responsibility principle—each part should have defined responsibility, regardless of being a method, class, package, or UI form. Such a part encompasses well-defined functionality without unnecessary external connections.

For instance, if coding application logic in the UI, you’re far from achieving this principle. You’re tying the UI with logic, unable to separate and work on them independently. In UI coding, place code relevant only to UI concerns—layout, field settings, reading, etc. Avoid complex data processing algorithms here; use external functions, methods, or classes for that.

Patterns like MVC and multi-layered architecture models assist in this case. Still, the common-sense application of responsibility principles, from which these designs emerge, is always beneficial.

I’d like to emphasize “common-sense.” Not every situation suits MVC or similar paradigms. Maybe 3% of cases justify coding logic in the UI, but that’s only 3%. Perhaps 5% or 10%, but it remains an exception!

Test, Modify, or Work on One Thing at a Time

Although mentioned earlier, this rule is critical enough to warrant its section. You might know the answer to “How do you eat an elephant?” “Bit by bit.” This principle must accompany us uncompromisingly in daily work—though it often doesn’t. Believe me; it doesn’t. Even I occasionally stray and disregard it.

Before tackling a major task (at least 3-4 hours), spend a few moments planning your actions. Plan so the elephant, big as it is, doesn’t choke you.

I remember my programming experiences, taking on tasks, developing complete solutions in hours, then launching them… for the first time. Of course, they never worked at that moment; they had no chance. Another few hours were spent debugging and fixing. I thought I could eat a whole elephant instantly.

What should you do at the start? Make a small plan to establish an approximate creation method segment by segment. Suppose you’re writing a neural network (if you don’t know much about it, don’t worry; it doesn’t overly impact the discussion). Don’t complete the solution. Implement the simplest things step by step. Initially, it might be useful to create a neuron simulation function. Perhaps later, create a neuron class, test its methods, then possibly implement the network, and backward propagation rule. It’s a sample sequence. It’s most important to develop or modify one thing at a time. Once it functions and tests are complete, move to the next. Consider the whole at the start (at least at a general level), but implement piece by piece, step by step. These micro-iterations—the interesting form for achieving this is Test-Driven Development (though not the only way, but certainly worth attention).

I’ve seen situations where tasks like implementing a report generation feature in a web application, based on system and current form data, were done simultaneously. Developers simultaneously worked on action (user interface event handling class), UI changes, and generating a PDF report (testing PDF generation). Imagine the mental bandwidth needed to handle all details and interactions when many elements interfere, often unfinished, complicating error sourcing.

Finding Error Causes

The above methods significantly reduce errors but be honest—errors will occur. If you apply the principle Test, modify, or work on one thing at a time, finding error causes becomes significantly easier. You make small steps developing code, easily identifying changes leading to errors. This principle works with sufficiently small steps. If, within the last hour, you’ve made about a dozen changes to config files, code, and HTML, pinpointing the error is difficult. However, if you first change one config and it works, then the second—they work, then make code changes—an error appears, the reason is clear.

Of course, life isn’t always rosy, and errors appear over time (e.g., days or even months later). In such cases, the proposed principle is:

Simplify the solution fragment until it works, then add elements until the error emerges.

Then, the situation will clarify.

Suppose this occurs:

An MVC-based application—the event fetches specific data to generate a PDF. There’s a component initialized in some way, performing this task:

  1. Retrieves data from data sources
  2. Generates an object to create a PDF
  3. Creates a PDF
  4. Sends the PDF to the screen

How to simplify? An example strategy (details will depend on specific cases). First, ensure the component used is correctly initialized. You can substitute another component (such as a simplified version for testing) or manually initialize it if it’s usually created from a config file. If the component setup wasn’t the cause, replace data-fetching with fabricated results (checking if data source results cause errors). Next, generate a dummy PDF object (or skip it). Instead of PDF creation (binary stream), try using simple character data (string stream) to rule out errors in PDF creation and screen display. During any stage where errors appear, analyze it using this method, limiting it to that step.

Logs and debugging systems are invaluable in error detection (though time-consuming in complex systems). They support the process described above, needing intellectual effort from us.

Similarly, techniques for other technologies or conditions alter differently. You’re an expert in your field, so you’ll know how to adapt this strategy to your environment.

On error detection—a final tip:

Error causes usually hide where we’re sure they don’t exist. The simplest and most common error cause is a typo.

Summary

The above examples aim to highlight the rule of simplifying the environment when solving a particular problem or task. Since these involve our habits and convictions, Reader, you may feel opposed to accepting their content or think they don’t apply to your case. It’s a natural response—these propositions belong to humanities, particularly social sciences and psychology. They’re not certainties. These theses often hold but don’t cover every case. Yet, exceptions prove the rule.

Examining habits and experimenting with proposed strategies can be enjoyable, increasing both productivity and the joy of creating software, which is my wish for you, Reader.

(Text translated and moved from original old blog automatically by AI. May contain inaccuracies.)

Related Posts

Estimation Is Not a Commitment

Estimation Is Not a Commitment

You’ve probably heard that estimation is not a commitment. Sometimes in teams using estimation techniques, some form of accountability for the accuracy of estimation emerges. This is unfavorable for several reasons:
a) firstly, estimation is an approximation (with some probability), not a definitive result;
b) secondly, when accountability kicks in, project games emerge;
c) there are at least several factors causing estimation to differ from the actual time spent on a given task:

Read More

Task-doing vs. Responsibility Taking - A Subtle Distinction

The Subtle Distinction Between Task-doing and Responsibility Taking

I have been reading a book on parenthood recently (yes, tech guys also read such books :-)) and there has been a discussion about responsibility. Even when fathers devote their time to spending time with children and doing some tasks related to children and family, they may still not take responsibility for it. So you can take your children to the doctor when they are sick, bring them to school or kindergarten every day, go with them to a playground… and still not take responsibility.

Read More

The Property of Complex Systems

The Property of Complex Systems

This morning, while driving to work, I encountered a much longer traffic jam than usual. “Well, with such cold weather, everyone is probably driving more cautiously,” I thought, as I slowly crawled along the Łazienkowska Route. After several minutes, I noticed from a distance that there was an accident on the opposite lane, with police and paramedics doing their work. On my side, nothing particular was happening. But still… Drivers were simply slowing down to see what was happening on the other side. No one was stopping, they were just looking. And as a result, the stretch that usually took me 5 minutes this time took 20 minutes. After passing this point, the traffic sped up significantly and flowed normally.

Read More