Software is a highly iterative entity, all the way to the 0s and 1s.
Boolean algebra was developed less than 200 years ago, and we have since built layer upon layer on top of this simple logical arithmetic. In this article, we'll take a look through ten software layers and how each layer relates to the layer before it. Note, though, that this is not exhaustive, and for the sake of brevity, we will gloss over many details. Please leave comments on areas you wished had more specifics, and we can discuss these in subsequent articles!
Binary is the core of software because it is the link to the hardware. The zero gets mapped to no electrical current, and the one gets mapped to electrical current present -- this mapping represents the core interaction layer between hardware and software.
While Binary is the core, without logic gates, software doesn't exist: there needs to be a way to modify a wire's 0 or 1 value, based on some logic! AND, OR, and NOT gates are what provide this logic. These three physical hardware components connect wires: AND & OR gates combine two input wires and produce a single output, while NOT gates combine an input to a single output. These gates provide the ability to transform a wire's voltage value, based on logic, which can be programmed. By threading wires through these gates in various combinations, we form circuits that can power endless programmed logic pieces.
Using logic gates to connect electrical wires and current whose voltage maps to binary 0s and 1s, circuits can be created with high precision nowadays by modern manufacturers. Specifically, we are at the nanometer scale for the accuracy of the circuitry we can produce. CPUs contain billions of logic gates, which allow for the sophisticated applications we run today, thanks to this miniaturization of circuitry.
Assembly language is how to communicate with a CPU through its specific set of instructions it supports. x86 architecture, which you may have heard of before, is an example set of instructions that specific CPUs support -- in this case, primarily Intel CPUs. A well-known alternative to x86 is ARM, another set of instructions for particular CPUs. Assembly language is barely human-readable, in that it includes letters & symbols we can recognize, but does not have the same syntactic niceties that most modern programming languages have. Assembly is all about registers and moving data from one memory address to another, the lowest of low-level programming.
Modern languages have become increasingly readable, with developer ergonomics a recent focus in the space. These languages all interact with the hardware & assembly language through an intermediary: a compiler. These software pieces translate higher-level languages into assembly code, in some cases with steps in between (as in, translation to other languages first, then to assembly). Compilers are highly complex pieces of software, as they translate the higher-order code written by software engineers into the logical register moves necessary to perform those actions.
There are many programming languages out there today, and it's easy to create new ones: define your syntax and write up the compiler! In reality, languages like Java or C++ that have been around for years would be hard to replicate. The amount of code that already exists to power the languages is enormous, and the difficulty in switching languages for large organizations already using a particular language is massive. That said, new languages spring up all the time, and most fall into one of two categories: Object-oriented or functional. All languages have similar basics, though, typically referred to as control flow: syntax that allows for the various core building blocks of programming to be specified. These include if/else statements, loops, and all the variations in between, including exceptions.
While there are two core types of programming languages, both use functions. It just so happens that everything is formulated as a function in a functional language, while object-oriented languages have, well, objects. These use functions too, but objects add a layer of abstraction on top of the core functional syntax shown in many software contexts.
Control flow and functions provide a mechanism to specify a large number of logical operations. Classes and interfaces allow for a layer on top of that, which feels more relatable to the analog world. Classes allow for creating logical groupings of functions and variables, which can mirror real-world objects and entities. Interfaces are further abstractions, which allow for classes to share functionality and inter-operate.
Services are what most consumers see as "software." These are collections of classes and functions that provide a complete, specific set of functionality. A group of use cases is fulfilled by software classes and functions, providing an end-user a particular service. Services can be as simple as a single class providing the functionality of a basic calculator, all the way to a piece of a much larger system.
It takes the combination of multiple services to form a complete system. These are used globally at scale in an endless variety of contexts. An application like Facebook is powered by a massive software system, made up of thousands of services, each providing specific functionality that, when combined, creates the Facebook experience. Software systems can incorporate just a handful of services or thousands. While this is the 10th layer described, the so-called "top", this layer alone contains many sublayers. Systems can combine other systems, creating ever more complex software entities that provide an ever expanding software capabilities set.
Software engineering can be daunting because finding one's footing in the mass of layers described is challenging. Software systems are just iterations off of the layers below, though, so while it may be intimidating, any software system can be progressively broken down, all the way down to the boolean logic that powers it's functionality. While it's not necessary to know about all of the layers all simultaneously, having an understanding of the layers adjacent to where you work daily is invaluable, as it helps connect dots that would otherwise prove frustrating to work through.