Thinking Better About Challenging Software Problems

Overview

Software engineering is a challenging task. No doubt about that.

We often need to deliver spectacular product features by a strict deadline. That is sometimes what stresses out developers.

Or you might be overwhelmed by the number of available techniques and need help moving to a final solution.

Anyone beginning in this industry feels that at some point. You often need help to provide a solution to a problem. That is normal.

Indeed, industry experience is valuable to become independent in your tasks. However, I found a few strategies to apply to any software development context to speed up the process of being more independent.

In this post, I'll show some strategies I've used (and still use) to work competently with software.

1. Deeply understand the problem to be solved

Create a draft. Make assumptions. Ask questions.

That you need to understand the problem you must solve is quite apparent. But, how to understand it might confuse someone at the beginning.

Gluing all the pieces to deliver a final solution is a challenging task.

Writing is one thing I always suggest (and probably because I'm a blogger). Writing and drawing, actually.

Your first write-ups and drawing of a solution don't have to be optimal. That's because the solution can (and it probably will) change with each new discovery.

Therefore, you don't need to spend time documenting everything because the requirements aren't apparent at this point. So, create a draft and iterate over it until all steps needed are done.

Now that you have started the draft go after the information! Here's a list that helps me to get information about the problem:

  1. Read the high-level requirements and ask what they mean to whoever wrote them. Ask about the specific definitions of things, like "What is a shopping cart?", "How would users use that shopping cart?", "There's a maximum of items in that cart?", "Why does the client need a shopping cart?".
  2. Investigate the candidate source code or the code you'll probably add to your solution. Give a first guess; there's no right or wrong in this phase. Imagine how the functionality fits into a code that you've seen before. Can you make it work by adding it there, or there's no chance of success adding it there?
  3. Write and draw the first set of observations, drawings, and questions that you need to answer to deliver the product. Then, use those observations and drawings to question other technical people and use their knowledge to foment your draft.

One crucial thing is questioning people with a draft in your hands with specific questions and initial findings about a possible solution. Avoid adding people in meetings to ask questions without any research previously done.

2. Divide and conquer: Break down the problem into smaller problems

Break the problems discovered into many sub-problems.

Now, you have an idea of how to solve the problem. You understand the requirements, know why you are solving them, and have a direction for the final solution. You might find something you didn't calculate in later phases, but that's part of the job.

Ok, you have a potential solution. That solution is always dividable. In other words, you can always think of a problem as a set of smaller problems. And the smaller part of a problem is always easier to solve than the whole problem.

So, how do we divide the problem? We can use our draft as a base for that division.

If you, like me, are used to drawing solutions as boxes and arrows, then each box is a potential division you can solve individually. And each arrow is the connection between one sub-problem and another. You can use Excalidraw for that:

Supposing you enjoy flowcharts, each step is a sub-problem, and the lines between them are how they connect.

If you like spreadsheets, each column would be a subproblem, and each line would be an order to solve each one.

The point is, whichever method of drafting you use, you'll end up with a list of problems to solve. Those problems are the subproblems you need to solve individually as a part of the final solution.

3. Start coding and iterate over the initial design

Solve each sub-problem separately in the code. Discover new things. Iterate the original design.

Finally, you have an initial feasible design. Everything is going fine; you presented the solution to your peers, and they liked it. Lots of questions were answered during your interviews. Thus, you can start coding something.

Yeah, you can start coding, but remember that the project design might not be done yet. You'll probably start finding things along the way that you didn't catch before. This is common in software problems. You don't know beforehand if something is correct. So, you should start doing and finding the gotchas during development.

During the first steps of development, after you have an initial design, try to identify the scenarios to which your code will be submitted. Those scenarios cover the majority of ways the user can interact with the system?

While touching the source code, identify the information that you have available there. Is it sufficient to address the sub-problem you're looking to solve?

When you finish a system component that solves a sub-problem of the original one, think of how those components communicate. Is it safe and reliable? What can go wrong? If something goes wrong, what can I do for the system to recuperate?

Iterating over the initial design while creating the code is a crucial step. You'll likely find things you didn't notice before. It could be an edge-case scenario you didn't expect your users to try. Or a component of the system that is not reliable.

In any case, it's essential to question the design decisions, learn from them, and propose something better.

4. Conclusion

In summary, use that framework to think better about software problems:

  1. Create a draft design, make initial assumptions, and ask questions.
  2. Break the problem into sub-problems and fit the solution for each sub-problem in your design.
  3. Start coding using the draft design as a starting point. Identify problems with the initial draft and iterate to improve the final solution.

Whole scheme for problem solving