The post has been translated automatically. Original language: Russian
One of the most powerful ideas of Unix is not only to make programs small. The real power comes when such programs can be freely connected to each other.
Instead of one huge system that knows everything, Unix offers a set of tools. Everyone solves their own problem, and more complex work is assembled from their sequence. It is this connectivity that has made many Unix programs so tenacious.
The program doesn't have to know the whole task.
Let's imagine a simple situation. There is a large text file from which you need to select lines with a specific word, sort them and count the number of unique values.
You can write a separate program specifically for this task. It will open the file, search for lines, sort, delete repetitions, and read the result.
Unix offers another way. One program reads the data, another selects the necessary lines, the third sorts, the fourth deletes repetitions, the fifth counts.
None of them knows why the whole job is being done. Everyone does only their part.
This is what allows you to use the same tools in dozens of other tasks.
Channel between programs
Unix has a simple mechanism that allows you to transfer the result of one program directly to the input of another. It is usually indicated by a vertical line.
For example, one program outputs text, the next receives this text as input and continues processing.
This is how the software pipeline arises.
It seems like a very simple solution, but the consequences are huge. Programs do not need to know about each other in advance. They are not required to use a common complex interface or a special interaction format. It is enough to agree on a clear data flow.
Instead of a close connection, a sequence of independent steps is obtained.
The complexity is transferred to the connection
When several simple programs are combined, an important effect occurs. The complexity of the entire task is no longer contained within one large component.
It is distributed between small parts and the way they are connected.
This is useful because each part can be understood separately. If the result is incorrect, you can check each step. If you need to change the processing, it is often enough to replace one program without touching the rest.
Such a system is easier to investigate and fix.
A good interface is more important than an internal device
When programs connect to each other, the boundary between them becomes especially important. Internally, each component can be arranged as you like, but externally it must behave in a clear and predictable manner.
That's why simple interfaces are so valuable. If a component receives understandable data and outputs a clear result, it can be replaced with another one without rebuilding the entire system.
This is one of the main signs of a good architecture. Parts of the system should depend not on the internal details of the neighbors, but on a clear agreement on cooperation.
Why do large programs get confused so quickly?
In a large monolithic system, borders often gradually disappear. One module starts using the other's data directly. The third one knows about the internal features of the first two. The fourth one counts on a specific order of calls.
As long as the system is small, it's almost unnoticeable. But over time, any change begins to affect more and more places.
Fragility arises. The component can no longer be replaced separately, because too many other parts know how it works inside.
The Unix approach tries to prevent this from the very beginning. Let the parts communicate across simple boundaries and know as little about each other as possible.
Composition is more important than versatility
There are two ways to make the system powerful.
The first is to create one huge tool that provides for all possible cases in advance.
The second is to create several simple tools that the user can combine in different combinations.
Unix relies on the second way.
This is an important difference. The universal program must guess in advance what the user will need. The composition allows a person to determine the desired sequence of actions himself.
As a result, the system can solve problems that the authors of individual tools did not even think about.
Unexpected possibilities come out of the simple parts.
One tool is being created to search for text. The other one is for sorting. The third one is for counting. Individually, they are quite limited.
But if they can be freely combined, the number of possible combinations becomes huge.
This is where real flexibility comes in. Sometimes a new task is solved not by a new program, but by a new combination of existing ones.
This is a very strong idea: to expand the capabilities of the system not only by adding code, but also in a new way to connect what is already there.
It works far beyond Unix.
Today, this approach is found everywhere.
Software libraries consist of separate functions. Network services provide clear interfaces. Data processing systems are built as a sequence of stages. Even routine automation is often a chain of small actions.
The shape has changed, but the principle is the same: small independent components are easier to connect, replace and reuse.
Therefore, the idea of Unix turned out to be much broader than the operating system itself.
But the separation can also become excessive.
It does not follow from the principle of composition that the system needs to be split up indefinitely.
If one simple process is divided into dozens of components, each of which communicates via a network, has separate settings and requires its own maintenance, the system may become more complex than the original monolith.
The problem is not solved by mechanically increasing the number of parts.
A good composition requires reasonable boundaries. The components should be independent enough to have clear responsibilities, but not so small that the interaction between them becomes more complicated than the work itself.
Unix is valuable not for the cult of small programs, but for the ability to find the right boundaries.
Microservices do not guarantee composition
Modern microservices are often compared to the Unix philosophy. There is indeed a similarity: the system is divided into separate components, each of which is responsible for its own area.
But the mere presence of multiple services does not guarantee anything.
If one service cannot be changed without simultaneously changing five others, there is no independence. If they constantly use internal neighbor data structures, the boundaries exist only formally.
True composition does not begin with the number of processes, but with the independence of the parts.
What is changing with artificial intelligence
Artificial intelligence development makes this principle particularly important.
It's very easy to ask a model to add a new feature directly to existing code. Then another one. Then another one. The code is growing fast because the cost of writing it has dropped dramatically.
But along with this, architectural discipline may disappear.
If each new feature is simply added to a place where similar code already exists, the system quickly turns into a large connected node. It works, but it's getting harder to understand.
It is much more reliable to define the boundaries first. Which component is responsible for this task? What data does it receive? What should I return? Is it possible to check it separately?
And only then assign artificial intelligence to write code.
Artificial intelligence also works better with parts.
There is another practical effect. The clearer the boundaries of a component, the easier it is to explain its task to artificial intelligence.
If you need to write a small function with a clear input and output, the result is easy to verify. If you need to change a huge system where the behavior is distributed over dozens of files, the probability of unexpected errors increases.
Therefore, the old Unix idea unexpectedly helps in modern development: first, divide the task into independent parts, and then work with each one separately.
This is useful for both humans and artificial intelligence.
The system must be assembled
A good architecture is not like a huge monolithic machine, but like a set of parts that can be connected in different ways.
Each component should have a clear purpose. The boundary between the components should be simple. The internal details of one part should not become mandatory knowledge for the rest.
class="paragraph"> Then the system is easier to develop. One component can be replaced. Another one to rewrite. Use the third one in a new task.
That's why the idea of composition survived Unix.
The strength of a system is determined not only by how good its individual parts are. It is equally important how easily these parts are connected.
Sometimes the best way to create a large system is not to write one large program at all.
Одна из самых сильных идей Unix состоит не только в том, чтобы делать программы небольшими. Настоящая сила появляется тогда, когда такие программы можно свободно соединять между собой.
Вместо одной огромной системы, которая знает обо всём, Unix предлагает набор инструментов. Каждый решает свою задачу, а более сложная работа собирается из их последовательности. Именно эта способность к соединению и сделала многие Unix-программы настолько живучими.
Программа не обязана знать всю задачу
Представим простую ситуацию. Есть большой текстовый файл, из которого нужно выбрать строки с определённым словом, отсортировать их и посчитать количество уникальных значений.
Можно написать отдельную программу специально для этой задачи. Она будет открывать файл, искать строки, сортировать, удалять повторы и считать результат.
Unix предлагает другой путь. Одна программа читает данные, другая отбирает нужные строки, третья сортирует, четвёртая удаляет повторы, пятая считает.
Ни одна из них не знает, зачем выполняется вся работа целиком. Каждая делает только свою часть.
Именно это позволяет использовать те же инструменты в десятках других задач.
Канал между программами
В Unix есть простой механизм, который позволяет передавать результат одной программы прямо на вход другой. Обычно он обозначается вертикальной чертой.
Например, одна программа выдаёт текст, следующая получает этот текст как входные данные и продолжает обработку.
Так возникает программный конвейер.
Это кажется очень простым решением, но последствия огромны. Программам не нужно заранее знать друг о друге. Они не обязаны использовать общий сложный интерфейс или специальный формат взаимодействия. Достаточно договориться о понятном потоке данных.
Вместо тесной связи получается последовательность независимых шагов.
Сложность переносится в соединение
Когда несколько простых программ объединяются, возникает важный эффект. Сложность всей задачи больше не находится внутри одного большого компонента.
Она распределяется между маленькими частями и способом их соединения.
Это полезно потому, что каждую часть можно понять отдельно. Если результат неправильный, можно проверить каждый шаг. Если нужно изменить обработку, часто достаточно заменить одну программу, не трогая остальные.
Такую систему легче исследовать и исправлять.
Хороший интерфейс важнее внутреннего устройства
Когда программы соединяются друг с другом, особенно важной становится граница между ними. Внутри каждый компонент может быть устроен как угодно, но снаружи он должен вести себя понятно и предсказуемо.
Именно поэтому простые интерфейсы так ценны. Если компонент получает понятные данные и выдаёт понятный результат, его можно заменить другим, не перестраивая всю систему.
Это один из главных признаков хорошей архитектуры. Части системы должны зависеть не от внутренних деталей соседей, а от ясного соглашения о взаимодействии.
Почему большие программы так быстро запутываются
В большой монолитной системе границы часто постепенно исчезают. Один модуль начинает напрямую использовать данные другого. Третий знает о внутренних особенностях первых двух. Четвёртый рассчитывает на конкретный порядок вызовов.
Пока система небольшая, это почти незаметно. Но со временем любое изменение начинает затрагивать всё больше мест.
Возникает хрупкость. Компонент уже нельзя заменить отдельно, потому что слишком много других частей знают, как он устроен внутри.
Unix-подход пытается не допустить этого с самого начала. Пусть части общаются через простые границы и знают друг о друге как можно меньше.
Композиция важнее универсальности
Есть два способа сделать систему мощной.
Первый — создать один огромный инструмент, в котором заранее предусмотрены все возможные случаи.
Второй — создать несколько простых инструментов, которые пользователь может соединять в разных комбинациях.
Unix делает ставку на второй путь.
Это важное различие. Универсальная программа должна заранее угадывать, что понадобится пользователю. Композиция позволяет человеку самому определить нужную последовательность действий.
В результате система может решать задачи, о которых авторы отдельных инструментов вообще не думали.
Из простых частей появляются неожиданные возможности
Один инструмент создаётся для поиска текста. Другой — для сортировки. Третий — для подсчёта. По отдельности они довольно ограничены.
Но если их можно свободно соединять, количество возможных комбинаций становится огромным.
Именно здесь появляется настоящая гибкость. Новая задача иногда решается не новой программой, а новой комбинацией уже существующих.
Это очень сильная идея: расширять возможности системы не только добавлением кода, но и новым способом соединить то, что уже есть.
Это работает далеко за пределами Unix
Сегодня подобный подход встречается повсюду.
Программные библиотеки состоят из отдельных функций. Сетевые службы предоставляют чёткие интерфейсы. Системы обработки данных строятся как последовательность этапов. Даже обычная автоматизация часто представляет собой цепочку небольших действий.
Форма изменилась, но принцип тот же: небольшие независимые компоненты легче соединять, заменять и повторно использовать.
Поэтому идея Unix оказалась гораздо шире самой операционной системы.
Но разделение тоже может стать чрезмерным
Из принципа композиции не следует, что систему нужно дробить бесконечно.
Если один простой процесс разбит на десятки компонентов, каждый из которых общается через сеть, имеет отдельные настройки и требует собственного обслуживания, система может стать сложнее исходного монолита.
Проблема не решается механическим увеличением количества частей.
Хорошая композиция требует разумных границ. Компоненты должны быть достаточно самостоятельными, чтобы иметь ясную ответственность, но не настолько мелкими, чтобы взаимодействие между ними стало сложнее самой работы.
Unix ценен не культом маленьких программ, а умением находить правильные границы.
Микросервисы не гарантируют композицию
Современные микросервисы часто сравнивают с философией Unix. Сходство действительно есть: система делится на отдельные компоненты, каждый из которых отвечает за свою область.
Но само наличие множества сервисов ещё ничего не гарантирует.
Если один сервис нельзя изменить без одновременного изменения пяти других, независимости нет. Если они постоянно используют внутренние структуры данных соседей, границы существуют только формально.
Настоящая композиция начинается не с количества процессов, а с независимости частей.
Что меняется с искусственным интеллектом
Разработка с помощью искусственного интеллекта делает этот принцип особенно важным.
Очень легко попросить модель добавить новую возможность прямо в существующий код. Потом ещё одну. Затем ещё одну. Код растёт быстро, потому что стоимость его написания резко снизилась.
Но вместе с этим может исчезнуть архитектурная дисциплина.
Если каждая новая функция просто добавляется туда, где уже есть похожий код, система быстро превращается в большой связанный узел. Она работает, но её всё труднее понимать.
Гораздо надёжнее сначала определить границы. Какой компонент отвечает за эту задачу? Какие данные он получает? Что должен вернуть? Можно ли проверить его отдельно?
И только потом поручать искусственному интеллекту писать код.
Искусственный интеллект тоже лучше работает с частями
Есть ещё один практический эффект. Чем яснее границы компонента, тем проще объяснить его задачу искусственному интеллекту.
Если нужно написать небольшую функцию с понятным входом и выходом, результат легко проверить. Если нужно изменить огромную систему, где поведение распределено по десяткам файлов, вероятность неожиданных ошибок растёт.
Поэтому старая Unix-идея неожиданно помогает и в современной разработке: сначала разделить задачу на независимые части, а затем работать с каждой отдельно.
Это полезно и человеку, и искусственному интеллекту.
Система должна быть собираемой
Хорошая архитектура похожа не на огромную монолитную машину, а на набор частей, которые можно соединять разными способами.
Каждый компонент должен иметь понятное назначение. Граница между компонентами должна быть простой. Внутренние детали одной части не должны становиться обязательным знанием для остальных.
Тогда систему легче развивать. Один компонент можно заменить. Другой переписать. Третий использовать в новой задаче.
Именно поэтому идея композиции пережила Unix.
Сила системы определяется не только тем, насколько хороши её отдельные части. Не менее важно, насколько легко эти части соединяются.
Иногда лучший способ создать большую систему — вообще не писать одну большую программу.