Introduction
Software is all about exploring and discovering, so in order to succeed at it, you need to be an expert in learning.
Bad ideas happen and in order to eliminate them, we must start being more skeptical about our ideas and find out how we can falsify them.
Software Engineering?
I like to consider software engineering as a person finding efficient solutions to problems in software. Wikipedia has a different definition than I do, but solving problems is one of the most important aspects of being a software engineer.
In order to find efficient solutions, we must continue learning. We must also have to become experts at managing complexity as systems can get very complex.
Birth of Software Engineering
Software engineering was created by Margaret Hamilton who lead the effort in developing the flight-control software for the Apollo program at NASA.
The earliest computers were programmed by flipping switches. Because of the fact that we gotta keep flipping switches, it became clear that this was slow. As a result the idea of a “stored program” was created.
Computer programs have become advanced and complex later on in the late 1960s. Computers were involved in solving complex problems.
The famous Moore’s law, created by Gordon Moore, predicted that transistor densities would double every year. This set the expectation of what semiconductors producers would meet.
Paradigm shift
The paradigm shift was created by Thomas Kuhn. The idea behind it was when we make a shift, we would discard other ideas that we know are not correct anymore.
This has given us more effective learning and discarding of bad ideas more effectively.
Engineering Approach
Engineering in different disciplines isn’t the same. Aerospace engineering is different than electrical engineering or chemical engineering, but all of them share the same fundamental idea.
Measurement
In order to discard bad ideas more effectively, we must measure our performance in software development effectively, but measuring in software development is difficult.
They’re either irrelevant or harmful.
We would need to measure throughput and stability. High stability and high throughput mean that a team is considered a “high performer” while low stability and throughput mean that teams are considered a “low performer”.
Stability is tracked through:
Recovery Failure Time: How long does it take for you to recover from a failure at a particular point?
Change Failure Rate: The rate at which a change has introduced an error at a particular point.
Measuring stability shows us how a team performs in delivering software with measurable quality. We just want the quality of the work being delivered.
Throughput is tracked through:
Frequency: How often is the change deployed in production? We would need to measure speed.
Lead Time: The time of the development process. How long does it take for a change to go from an idea to working?
Measuring throughput shows us the team’s efficiency in delivering ideas.
Throughput and stability answer the questions of quality and how fast teams can produce software of that quality.
We do still need to think carefully. We need to know the meaning of the results.
But not everything is perfect since they don’t answer the question of, whether is the team working on the right problem and building the right solution.
Learning
As said above, software development is all about exploring and discovering. We need to learn more about what our users or customers want from the system, how to effectively use tools and techniques, and how to solve problems that are presented to us.
There are also other things we got to learn such as learning how to organize ourselves, understanding the problem more deeply, and fixing things such as bugs.
Learning is the heart of basically everything we do.
Complexity
We need to manage the complexity of the systems that we create. Technically if you’re just creating a throwaway system, then the quality wouldn’t matter. If you want to build something complex, then it’s better to divide problems into smaller chunks. A small problem is less scary than a big problem, so work small and just build up.
Work Iteratively
Iteration is defined as “the repetition of a process in order to generate a sequence of outcomes” from Wikipedia. Iteration, in short, is just doing the same thing over and over again, improving along the way.
Iteration is what helps us improve and correct our mistakes.
Advantages of Iteratively
Iteratively helps narrow our focus and helps us think in smaller batches. We could iterate on a product and change them, based on great feedback from our users and customers.
By working in smaller steps, we can get reactions from the users/customers in which we can validate our ideas. We want to put low investments into smaller projects, so if things don’t work out then at least the company won’t go bankrupt. If it does work out, then we can continue putting investment into the project. This is how we can test out our ideas with minimum cost.
Large projects are a risk because if they were to end up badly, they will threaten the company into bankruptcy or out of existence, so start with smaller projects.
Use this as a way to explore what the team should work on and try out technologies. You can still make progress even if the technology was a bad idea and everything fails since we want to learn from our mistakes. The only difference is that the mistake cost a little to our investment since we’re working in smaller steps.
We can continue it and start over. With this, we can continue improving, and enhancing our ideas, our product, and our skills.
How to work iteratively?
The first step is to work in smaller batches. This will allow us to test our new ideas, techniques, and technologies.
We want to write a code, test it, and see if it fails.
Then we want to modify the code to make it more clear. Run the test after you have modified the code and continue testing after every tiny change to see if it passes.
Feedback
Feedback is defined as “when outputs of a system are routed back as inputs as part of a chain of cause-and-effect that forms a circuit or loop” from Wikipedia.
Without feedback, there really isn’t any learning. We can’t tell if we’re making progress or not.
Feedback gives us evidence of our source. Once we have evidence, we can make better decisions.
Coding Feedback
When writing code, it’s important to test it. Run the test to see if it fails. The failure would give you feedback on what to change or if it needs correction.
Also, every time that you have made a tiny change, test it to see if the code still works after the change.
Remember that more code doesn’t mean better code; it oftentimes means worse code.
Early Feedback
It is an effective practice to get feedback as early as possible.
Development tools that can highlight errors are a good way to get feedback on your code.
Continuous integration and delivery are what will help us maximize the quality and speed of the feedback that we get.
Feedbacks that are misleading or late are useless.
Incrementalism
Incrementalism is defined as “a method of working by adding to a project using many small incremental changes instead of a few large jumps” from Wikipedia.
In simple terms, incrementalism is all about building with small steps. If iteratively is about repetition then incrementalism is about building it, piece by piece.
Advantage of Incrementalism
An incrementalism approach means that teams can work more independently. This will help us move forward and innovate a lot faster.
By requiring small steps, we can have small teams, and smaller teams are a lot easier to manage than large teams.
Tools for Incrementalism
The most profound tools for incrementalism are experimentation and feedback, but there are more ideas that can help us achieve incrementalism.
One idea is to improve refactoring skills. Refactoring helps us make small, simple, controlled steps that can help modify the code safely.
There is also testing that will help us make changes more quickly. The test should be simple as possible and the code should be testable.
Empiricism
Empiricism is defined as an “epistemological theory that holds that knowledge or justification comes only or primarily from sensory experience” from Wikipedia.
In short, empiricism is learning that comes from experience. Empiricism is related to experimenting since in order to experience things, we must experiment with them.
Reality
The most important aspect of a scientific approach to solving problems is the idea of skepticism. If an idea is bad, it is bad. It doesn’t matter where it came from, who thought of it, how much work we put into the idea, and how we wish it was true.
Empiricism is based on experimenting and experiencing things. Once we have experimented with enough things, we can make decisions based on evidence and observations that are true.
Experiment by making a hypothesis and then testing it out. Observe the results and see if they match the hypothesis or not. Repeat with a new hypothesis if things don’t work out.
Don’t jump to conclusions, it is slower by experimenting, but it is more effective at solving problems.
Richard Feynman once said, “The first principle is that you must not fool yourself and you are the easiest person to fool.”
The best way to start is to assume that what you know is wrong, and then figure out how you can prove it is wrong.
Empiricism is what will help us sense-check the validity of the experiments.
Experimental
Experimental is defined as “a procedure carried out to support or refute a hypothesis, or determine the efficacy or likelihood of something previously untried” from Wikipedia.
Move away from making decisions based on what the most important people have said, and instead, make decisions based on evidence.
Being experimental means:
Hypothesis: What we are aiming to evaluate?
Measurement: How we will evaluate the prediction that we are testing? What does success or failure mean in this context?
Control variables: Eliminate variables that are useless and serve no purpose.
Feedback: How we will collect results and then use those results back to the point at which we’re thinking.
Modularity
Modularity is defined as “a measure of the structure of networks or graphs which measures the strength of division of a network into modules” from Wikipedia.
Modularity is simply dividing the system into smaller pieces that are easy to understand. Modularity is important in managing complexity.
We are looking to divide the code into smaller compartments and each compartment can be reused a lot of times. The compartments will be simple to understand. Each function, class, and/or method should be readable and simple.
Once you have good modularity then you’ll notice that it is easier to modify, easier to test, and easier to work on.
One way to divide the system into smaller pieces is just by reducing the dependencies between teams. Teams can independently build, test, and deploy their work without relying on another team.
Cohesion
Cohesion is defined as “the action or property of like molecules sticking together, being mutually attractive” from Wikipedia.
In simple terms, it means that we should put things closer if they’re related. Things that are unrelated to each other should be further apart
Having a good design in software means that we should organize the code in the systems. We need to build systems that are easily testable, and easily understandable. Pull unrelated things out and pull related things in.
Code should be able to communicate with developers. Yeah, it also needs to be executable from the machine's perspective, but the goal of writing code should be communicating. By communicating, I mean that code should be able to explain itself clearly and simply. Code can’t just change itself, therefore the developers need to write code in a way to make it readable.
If you have ever read a code and thought that it was hard to read or you didn’t know what the code does, it probably means cohesion is lacking.
Separation of Concerns
Separation of Concerns is defined as “a design principle for separating a computer program into distinct sections” from Wikipedia.
In simple terms, it means that one section should address one thing. Separation of Concerns is what will help us improve the cohesion and modularity of our systems.
If your class does more than one thing, you aren’t achieving separation of concerns.
A good way to achieve separation of concerns is by using dependency injection. Dependency injection is where an object would use another object rather than creating a new one from scratch.
Another approach is to focus on separating concerns that are essential and accidental. Essential is solving the problem that you’re trying to solve while accidental is the problem that we’re forced to solve, like a side effect of the code for example. Accidental is important to manage, but if we were to build a system that solves accidental problems and the system didn’t solve any essential problem, it would be useless. So the main idea is to work on essential, but not ignore accidental.
Abstraction
Abstraction is defined as “The process of removing or generalizing physical, spatial, or temporal details or attributes in the study of objects or systems to focus attention on details of greater importance” from Wikipedia.
In simple terms, it just means hiding information that is deemed unnecessary. We got to focus on code that is right in front of us without worrying about anything else happening outside.
The job of a software developer is to solve problems, which requires us to design solutions.
We need to make it so that when we change one part of the code, it doesn’t affect the others. We need to make it so we can still understand the code even after four months. We got quickly and validate that our changes to the code are safe.
[END]
"We need to make it so that when we change one part of the code, it doesn’t affect the others. We need to make it so we can still understand the code even after four months." This is the craft part that so many software engineers miss.