Showing preview only (287K chars total). Download the full file or copy to clipboard to get everything.
Repository: zakirullin/cognitive-load
Branch: main
Commit: 71206faf1286
Files: 11
Total size: 278.8 KB
Directory structure:
gitextract_sp1lepdz/
├── LICENSE
├── README.es.md
├── README.ja.md
├── README.ko.md
├── README.md
├── README.np.md
├── README.prompt.md
├── README.pt-br.md
├── README.tr.md
├── README.vi.md
└── README.zh-cn.md
================================================
FILE CONTENTS
================================================
================================================
FILE: LICENSE
================================================
Copyright (c) 2023 Artem Zakirullin (https://github.com/zakirullin)
Attribution 4.0 International
=======================================================================
Creative Commons Corporation ("Creative Commons") is not a law firm and
does not provide legal services or legal advice. Distribution of
Creative Commons public licenses does not create a lawyer-client or
other relationship. Creative Commons makes its licenses and related
information available on an "as-is" basis. Creative Commons gives no
warranties regarding its licenses, any material licensed under their
terms and conditions, or any related information. Creative Commons
disclaims all liability for damages resulting from their use to the
fullest extent possible.
Using Creative Commons Public Licenses
Creative Commons public licenses provide a standard set of terms and
conditions that creators and other rights holders may use to share
original works of authorship and other material subject to copyright
and certain other rights specified in the public license below. The
following considerations are for informational purposes only, are not
exhaustive, and do not form part of our licenses.
Considerations for licensors: Our public licenses are
intended for use by those authorized to give the public
permission to use material in ways otherwise restricted by
copyright and certain other rights. Our licenses are
irrevocable. Licensors should read and understand the terms
and conditions of the license they choose before applying it.
Licensors should also secure all rights necessary before
applying our licenses so that the public can reuse the
material as expected. Licensors should clearly mark any
material not subject to the license. This includes other CC-
licensed material, or material used under an exception or
limitation to copyright. More considerations for licensors:
wiki.creativecommons.org/Considerations_for_licensors
Considerations for the public: By using one of our public
licenses, a licensor grants the public permission to use the
licensed material under specified terms and conditions. If
the licensor's permission is not necessary for any reason--for
example, because of any applicable exception or limitation to
copyright--then that use is not regulated by the license. Our
licenses grant only permissions under copyright and certain
other rights that a licensor has authority to grant. Use of
the licensed material may still be restricted for other
reasons, including because others have copyright or other
rights in the material. A licensor may make special requests,
such as asking that all changes be marked or described.
Although not required by our licenses, you are encouraged to
respect those requests where reasonable. More_considerations
for the public:
wiki.creativecommons.org/Considerations_for_licensees
=======================================================================
Creative Commons Attribution 4.0 International Public License
By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons
Attribution 4.0 International Public License ("Public License"). To the
extent this Public License may be interpreted as a contract, You are
granted the Licensed Rights in consideration of Your acceptance of
these terms and conditions, and the Licensor grants You such rights in
consideration of benefits the Licensor receives from making the
Licensed Material available under these terms and conditions.
Section 1 -- Definitions.
a. Adapted Material means material subject to Copyright and Similar
Rights that is derived from or based upon the Licensed Material
and in which the Licensed Material is translated, altered,
arranged, transformed, or otherwise modified in a manner requiring
permission under the Copyright and Similar Rights held by the
Licensor. For purposes of this Public License, where the Licensed
Material is a musical work, performance, or sound recording,
Adapted Material is always produced where the Licensed Material is
synched in timed relation with a moving image.
b. Adapter's License means the license You apply to Your Copyright
and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License.
c. Copyright and Similar Rights means copyright and/or similar rights
closely related to copyright including, without limitation,
performance, broadcast, sound recording, and Sui Generis Database
Rights, without regard to how the rights are labeled or
categorized. For purposes of this Public License, the rights
specified in Section 2(b)(1)-(2) are not Copyright and Similar
Rights.
d. Effective Technological Measures means those measures that, in the
absence of proper authority, may not be circumvented under laws
fulfilling obligations under Article 11 of the WIPO Copyright
Treaty adopted on December 20, 1996, and/or similar international
agreements.
e. Exceptions and Limitations means fair use, fair dealing, and/or
any other exception or limitation to Copyright and Similar Rights
that applies to Your use of the Licensed Material.
f. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public
License.
g. Licensed Rights means the rights granted to You subject to the
terms and conditions of this Public License, which are limited to
all Copyright and Similar Rights that apply to Your use of the
Licensed Material and that the Licensor has authority to license.
h. Licensor means the individual(s) or entity(ies) granting rights
under this Public License.
i. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material
available to the public including in ways that members of the
public may access the material from a place and at a time
individually chosen by them.
j. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world.
k. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning.
Section 2 -- Scope.
a. License grant.
1. Subject to the terms and conditions of this Public License,
the Licensor hereby grants You a worldwide, royalty-free,
non-sublicensable, non-exclusive, irrevocable license to
exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or
in part; and
b. produce, reproduce, and Share Adapted Material.
2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public
License does not apply, and You do not need to comply with
its terms and conditions.
3. Term. The term of this Public License is specified in Section
6(a).
4. Media and formats; technical modifications allowed. The
Licensor authorizes You to exercise the Licensed Rights in
all media and formats whether now known or hereafter created,
and to make technical modifications necessary to do so. The
Licensor waives and/or agrees not to assert any right or
authority to forbid You from making technical modifications
necessary to exercise the Licensed Rights, including
technical modifications necessary to circumvent Effective
Technological Measures. For purposes of this Public License,
simply making modifications authorized by this Section 2(a)
(4) never produces Adapted Material.
5. Downstream recipients.
a. Offer from the Licensor -- Licensed Material. Every
recipient of the Licensed Material automatically
receives an offer from the Licensor to exercise the
Licensed Rights under the terms and conditions of this
Public License.
b. No downstream restrictions. You may not offer or impose
any additional or different terms or conditions on, or
apply any Effective Technological Measures to, the
Licensed Material if doing so restricts exercise of the
Licensed Rights by any recipient of the Licensed
Material.
6. No endorsement. Nothing in this Public License constitutes or
may be construed as permission to assert or imply that You
are, or that Your use of the Licensed Material is, connected
with, or sponsored, endorsed, or granted official status by,
the Licensor or others designated to receive attribution as
provided in Section 3(a)(1)(A)(i).
b. Other rights.
1. Moral rights, such as the right of integrity, are not
licensed under this Public License, nor are publicity,
privacy, and/or other similar personality rights; however, to
the extent possible, the Licensor waives and/or agrees not to
assert any such rights held by the Licensor to the limited
extent necessary to allow You to exercise the Licensed
Rights, but not otherwise.
2. Patent and trademark rights are not licensed under this
Public License.
3. To the extent possible, the Licensor waives any right to
collect royalties from You for the exercise of the Licensed
Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties.
Section 3 -- License Conditions.
Your exercise of the Licensed Rights is expressly made subject to the
following conditions.
a. Attribution.
1. If You Share the Licensed Material (including in modified
form), You must:
a. retain the following if it is supplied by the Licensor
with the Licensed Material:
i. identification of the creator(s) of the Licensed
Material and any others designated to receive
attribution, in any reasonable manner requested by
the Licensor (including by pseudonym if
designated);
ii. a copyright notice;
iii. a notice that refers to this Public License;
iv. a notice that refers to the disclaimer of
warranties;
v. a URI or hyperlink to the Licensed Material to the
extent reasonably practicable;
b. indicate if You modified the Licensed Material and
retain an indication of any previous modifications; and
c. indicate the Licensed Material is licensed under this
Public License, and include the text of, or the URI or
hyperlink to, this Public License.
2. You may satisfy the conditions in Section 3(a)(1) in any
reasonable manner based on the medium, means, and context in
which You Share the Licensed Material. For example, it may be
reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required
information.
3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent
reasonably practicable.
4. If You Share Adapted Material You produce, the Adapter's
License You apply must not prevent recipients of the Adapted
Material from complying with this Public License.
Section 4 -- Sui Generis Database Rights.
Where the Licensed Rights include Sui Generis Database Rights that
apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database;
b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database
Rights, then the database in which You have Sui Generis Database
Rights (but not its individual contents) is Adapted Material; and
c. You must comply with the conditions in Section 3(a) if You Share
all or a substantial portion of the contents of the database.
For the avoidance of doubt, this Section 4 supplements and does not
replace Your obligations under this Public License where the Licensed
Rights include other Copyright and Similar Rights.
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
c. The disclaimer of warranties and limitation of liability provided
above shall be interpreted in a manner that, to the extent
possible, most closely approximates an absolute disclaimer and
waiver of all liability.
Section 6 -- Term and Termination.
a. This Public License applies for the term of the Copyright and
Similar Rights licensed here. However, if You fail to comply with
this Public License, then Your rights under this Public License
terminate automatically.
b. Where Your right to use the Licensed Material has terminated under
Section 6(a), it reinstates:
1. automatically as of the date the violation is cured, provided
it is cured within 30 days of Your discovery of the
violation; or
2. upon express reinstatement by the Licensor.
For the avoidance of doubt, this Section 6(b) does not affect any
right the Licensor may have to seek remedies for Your violations
of this Public License.
c. For the avoidance of doubt, the Licensor may also offer the
Licensed Material under separate terms or conditions or stop
distributing the Licensed Material at any time; however, doing so
will not terminate this Public License.
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
License.
Section 7 -- Other Terms and Conditions.
a. The Licensor shall not be bound by any additional or different
terms or conditions communicated by You unless expressly agreed.
b. Any arrangements, understandings, or agreements regarding the
Licensed Material not stated herein are separate from and
independent of the terms and conditions of this Public License.
Section 8 -- Interpretation.
a. For the avoidance of doubt, this Public License does not, and
shall not be interpreted to, reduce, limit, restrict, or impose
conditions on any use of the Licensed Material that could lawfully
be made without permission under this Public License.
b. To the extent possible, if any provision of this Public License is
deemed unenforceable, it shall be automatically reformed to the
minimum extent necessary to make it enforceable. If the provision
cannot be reformed, it shall be severed from this Public License
without affecting the enforceability of the remaining terms and
conditions.
c. No term or condition of this Public License will be waived and no
failure to comply consented to unless expressly agreed to by the
Licensor.
d. Nothing in this Public License constitutes or may be interpreted
as a limitation upon, or waiver of, any privileges and immunities
that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority.
=======================================================================
Creative Commons is not a party to its public
licenses. Notwithstanding, Creative Commons may elect to apply one of
its public licenses to material it publishes and in those instances
will be considered the “Licensor.” The text of the Creative Commons
public licenses is dedicated to the public domain under the CC0 Public
Domain Dedication. Except for the limited purpose of indicating that
material is shared under a Creative Commons public license or as
otherwise permitted by the Creative Commons policies published at
creativecommons.org/policies, Creative Commons does not authorize the
use of the trademark "Creative Commons" or any other trademark or logo
of Creative Commons without its prior written consent including,
without limitation, in connection with any unauthorized modifications
to any of its public licenses or any other arrangements,
understandings, or agreements concerning use of licensed material. For
the avoidance of doubt, this paragraph does not form part of the
public licenses.
Creative Commons may be contacted at creativecommons.org.
================================================
FILE: README.es.md
================================================
# La carga cognitiva es lo que importa
[Prompt](https://github.com/zakirullin/cognitive-load/blob/main/README.prompt.md) | [Blog version](https://minds.md/zakirullin/cognitive) | [Chinese](https://github.com/zakirullin/cognitive-load/blob/main/README.zh-cn.md) | [Korean](README.ko.md) | [Turkish](README.tr.md) | [Japanese](README.ja.md) | [Spanish](README.es.md)
*Este es un documento "vivo", última actualización: octubre de 2025. ¡Tus contribuciones son bienvenidas!*
## Introducción
Existen muchas palabras de moda y “mejores prácticas” por ahí, pero la mayoría han fracasado. Fracasaron porque fueron imaginadas, no reales. Estas ideas se basaban en la estética y en juicios subjetivos. Necesitamos algo más fundamental, algo que no pueda estar equivocado.
A veces sentimos confusión al leer el código. La confusión cuesta tiempo y dinero. La confusión es causada por una alta *carga cognitiva*. No es un concepto elegante y abstracto, sino **una limitación humana fundamental**. No es imaginaria, está ahí y podemos sentirla.
Como pasamos mucho más tiempo leyendo y entendiendo código que escribiéndolo, deberíamos preguntarnos constantemente si estamos incorporando una carga cognitiva excesiva en nuestro código.
## Carga cognitiva
> La carga cognitiva es cuánto necesita pensar un desarrollador para completar una tarea.
Al leer código, incorporas en tu mente cosas como los valores de las variables, la lógica del flujo de control y las secuencias de llamadas. La persona promedio puede mantener aproximadamente [cuatro de estos fragmentos](https://github.com/zakirullin/cognitive-load/issues/16) en la memoria de trabajo. Una vez que la carga cognitiva alcanza este umbral, se vuelve mucho más difícil entender las cosas.
*Supongamos que se nos ha pedido hacer algunas correcciones en un proyecto completamente desconocido. Nos dijeron que un desarrollador muy inteligente había contribuido en él. Se usaron muchas arquitecturas geniales, bibliotecas elegantes y tecnologías de moda. En otras palabras, **el autor había creado una alta carga cognitiva para nosotros.***
<div align="center">
<img src="/img/cognitiveloadv6.png" alt="Cognitive load" width="750">
</div>
Deberíamos reducir la carga cognitiva en nuestro proyectos tanto como sea posible.
<details>
<summary><b>Carga cognitiva e interrupciones</b></summary>
<div align="center">
<img src="img/interruption.jpeg" width="480">
</div>
</details>
> Vamos a utilizar "carga cognitiva" en un sentido informal; a veces se alineará con el concepto científico específico de Carga Cognitiva, pero no sabemos lo suficiente sobre dónde coincide y dónde no.
## Tipos de carga cognitiva
**Intrínseca**: causada por la dificultad inherente de una tarea. No se puede reducir; es la esencia misma del desarrollo de software.
**Extraña**: generada por la forma en que se presenta la información. Causada por factores que no son directamente relevantes para la tarea, como las peculiaridades del autor. Se pueden reducir considerablemente. Nos centraremos en este tipo de carga cognitiva.
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="Intrínsica vs Extraña" width="600">
</div>
Pasemos directamente a los ejemplos prácticos concretos de carga cognitiva extraña.
---
Nos referiremos al nivel de carga cognitiva de la siguiente manera:
`🧠`: memoria de trabajo fresca, carga cognitiva cero
`🧠++`: dos datos en nuestra memoria de trabajo, carga cognitiva aumentada
`🤯`: sobrecarga cognitiva, más de 4 datos
> Nuestro cerebro es mucho más complejo e inexplorado, pero podemos seguir con este modelo simplista.
## Condicionales complejas
```go
if val > algunaConstante // 🧠+
&& (condicion2 || condicion3) // 🧠+++, la condición anterior debe ser verdadera, una de c2 o c3 debe ser verdadera
&& (condicion4 && !condicion5) { // 🤯, estamos jodidos en este punto
...
}
```
Introduzca variables intermedias con nombres significativos:
```go
esValido = val > algunaConstante
esPermitido = condicion2 || condicion3
esSeguro = condicion4 && !condicion5
// 🧠, no necesitamos recordar las condiciones, hay variables descriptivas
if esValido && isPermitido && esSeguro {
...
}
```
## ifs anidados
```go
if esValido { // 🧠+, okay, el código anidado se aplica solo a entradas válidas
if esSeguro { // 🧠++, hacemos cosas solo para información válida y segura
cosas // 🧠+++
}
}
```
Compárelo con los resultados de antes:
```go
if !esValido
return
if !esSeguro
return
// 🧠, realmente no nos importan los retornos tempranos, si estamos aquí entonces todo bien
cosas // 🧠+
```
Podemos centrarnos únicamente en el camino feliz, liberando así nuestra memoria de trabajo de todo tipo de precondiciones.
## La pesadilla de la herencia
Se nos pide que cambiemos algunas cosas para nuestros usuarios administradores: `🧠`
`AdminController extends UserController extends GuestController extends BaseController`
Ohh, parte de la funcionalidad está en `BaseController`, echemos un vistazo: `🧠+`
Se introdujeron las mecánicas de roles básicos en `GuestController`: `🧠++`
Las cosas se modificaron parcialmente en `UserController`: `🧠+++`
Por fin estamos aquí, `AdminController`, ¡vamos a programar cosas! `🧠++++`
Ah, espera, existe `SuperuserController`, que extiende `AdminController`. Al modificar `AdminController`, podemos romper cosas en la clase heredada, así que analicemos primero `SuperuserController`: `🤯`
Prefiere la composición a la herencia. No entraremos en detalles - hay [mucho material](https://www.youtube.com/watch?v=hxGOiiR9ZKg) disponible al respecto.
## Demasiados métodos, clases o módulos pequeños
> Método, clase y módulo son intercambiables en este contexto.
Mantras como "los métodos deben ser más cortos que 15 líneas de código" o "las clases deben ser pequeñas" resultaron ser algo erróneos.
**Módulo profundo**: interfaz simple, funcionalidad compleja.
**Módulo superficial**: interfaz relativamente compleja en comparación con la pequeña funcionalidad que ofrece.
<div align="center">
<img src="/img/deepmodulev8.png" alt="Módulo profundo" width="700">
</div>
Tener demasiados módulos superficiales puede dificultar la comprensión del proyecto. **No solo debemos tener en cuenta las responsabilidades de cada módulo, sino también todas sus interacciones.** Para comprender el propósito de un módulo superficial, primero debemos analizar la funcionalidad de todos los módulos relacionados. Saltar entre componentes tan superficiales es mentalmente agotador; el <a target="_blank" href="https://blog.separateconcerns.com/2023-09-11-linear-code.html">pensamiento lineal</a> es más natural para nosotros los humanos.
> Ocultar información es de suma importancia y no ocultamos tanta complejidad en módulos superficiales.
Tengo dos proyectos personales, ambos con unas 5000 líneas de código. El primero tiene 80 clases superficiales, mientras que el segundo solo 7 clases profundas. No he mantenido ninguno de estos proyectos durante un año y medio.
Al regresar, me di cuenta de lo extremadamente difícil que era desentrañar todas las interacciones entre esas 80 clases del primer proyecto. Tendría que reconstruir una enorme carga cognitiva antes de poder empezar a programar. Por otro lado, pude comprender el segundo proyecto rápidamente, ya que solo tenía unas pocas clases profundas con una interfaz sencilla.
> Los mejores componentes son aquellos que proporcionan una funcionalidad potente pero tienen una interfaz sencilla.
>
> *John Ousterhout, Una filosofía del diseño de software*
La interfaz de I/O de Unix es muy sencilla. Solo tiene cinco llamadas básicas:
```python
open(path, flags, permissions)
read(fd, buffer, count)
write(fd, buffer, count)
lseek(fd, offset, referencePosition)
close(fd)
```
Una implementación moderna de esta interfaz tiene **cientos de miles de líneas de código**. Aunque esconde mucha complejidad, es fácil de usar gracias a su interfaz sencilla.
> Este ejemplo de módulo profundo se extrae del libro [Una filosofía del diseño de software](https://web.stanford.edu/~ouster/cgi-bin/book.php) de John Ousterhout. Este libro no solo aborda la esencia misma de la complejidad en el desarrollo de software, sino que también ofrece la mejor interpretación del influyente artículo de Parnas [Sobre los criterios que se deben utilizar para descomponer sistemas en módulos](https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf). Ambos son lecturas esenciales. Otras lecturas relacionadas: [Una filosofía del diseño de software vs. código limpio](https://github.com/johnousterhout/aposd-vs-clean-code), [Probablemente sea hora de dejar de recomendar código limpio](https://qntm.org/clean), [Funciones pequeñas consideradas dañinas](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29).
<details>
<summary><b>Las cosas importantes deben ser grandes, ejemplos</b></summary>
<br>
<div align="center">
<img src="/img/dirty.png" alt="Limpio vs Sucio" width="600">
</div>
<blockquote>Si permites que tus funciones "cruciales" importantes sean más grandes ("sucias") es más fácil distinguirlas del mar de funciones, son obviamente importantes: ¡solo míralas, son grandes!</blockquote>
Esta imagen está tomada del artículo <a href="https://htmx.org/essays/codin-dirty/" target="_blank">Codin' Dirty</a> de Carson Gross. Allí encontrarás <a href="https://htmx.org/essays/codin-dirty/#real-world-examples" target="_blank">ejemplos del mundo real</a> de funciones profundas.
</details>
P.D. Si crees que apoyamos objetos divinos inflados y con demasiadas responsabilidades, te equivocas.
## Responsable de una cosa
Con demasiada frecuencia, terminamos creando muchos módulos superficiales, siguiendo el principio impreciso de que «un módulo debe ser responsable de una sola cosa». ¿Qué es esta cosa difusa? Instanciar un objeto es una cosa, ¿verdad? Entonces, [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks) parece estar bien. **Los nombres e interfaces de dichas clases tienden a ser más exigentes mentalmente que sus implementaciones completas, ¿qué tipo de abstracción es esa?** Algo salió mal.
Realizamos cambios en nuestros sistemas para satisfacer a nuestros usuarios y actores. Somos responsables ante ellos.
> Un módulo debe ser responsable ante un solo usuario o actor.
De esto se trata el Principio de Responsabilidad Única. En pocas palabras, si introducimos un error en un lugar y luego dos profesionales distintos vienen a quejarse, hemos violado el principio. No tiene nada que ver con la cantidad de tareas que realizamos en nuestro módulo.
Pero incluso ahora, esta regla puede ser más perjudicial que beneficiosa. Este principio puede entenderse de tantas maneras diferentes como individuos. Un mejor enfoque sería analizar la carga cognitiva que genera. Es mentalmente exigente recordar que un cambio en un lugar puede desencadenar una cadena de reacciones en diferentes áreas del negocio. Y eso es todo, sin términos complejos que aprender.
## Demasiados microservicios superficiales
Este principio de módulo superficial-profundo es independiente de la escala y puede aplicarse a la arquitectura de microservicios. Demasiados microservicios superficiales no servirán de nada: la industria se encamina hacia "macroservicios", es decir, servicios que no son tan superficiales (=profundos). Uno de los fenómenos más graves y difíciles de solucionar es el llamado monolito distribuido, que a menudo resulta de esta separación superficial excesivamente granular.
En una ocasión, asesoré a una startup donde un equipo de cinco desarrolladores introdujo 17(!) microservicios. Llevaban 10 meses de retraso y estaban lejos de su lanzamiento público. Cada nuevo requisito implicaba cambios en 4+ microservicios. Reproducir y depurar un problema en un sistema tan distribuido requería muchísimo tiempo. Tanto el tiempo de comercialización como la carga cognitiva eran inaceptablemente altos. `🤯`
¿Es esta la manera correcta de abordar la incertidumbre de un nuevo sistema? Es extremadamente difícil establecer los límites lógicos correctos al principio. La clave está en tomar decisiones lo más tarde posible, ya que es cuando se dispone de más información. Al introducir una capa de red desde el comienzo, dificultamos la reversión de nuestras decisiones de diseño desde el principio. La única justificación del equipo fue: «Las empresas FAANG demostraron la eficacia de la arquitectura de microservicios». *Hola, tienen que dejar de soñar a lo grande.*
El [debate Tanenbaum-Torvalds](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate) argumentó que el diseño monolítico de Linux era defectuoso y obsoleto, y que debería utilizarse una arquitectura de micronúcleo. De hecho, el diseño de micronúcleo parecía ser superior tanto desde un punto de vista teórico como estético. En la práctica, tres décadas después, GNU Hurd, basado en micronúcleo, sigue en desarrollo, y Linux monolítico está en todas partes. Esta página funciona con Linux, tu tetera inteligente funciona con Linux. Con Linux monolítico.
Un monolito bien diseñado con módulos verdaderamente aislados suele ser mucho más flexible que un conjunto de microservicios. Además, requiere mucho menos esfuerzo cognitivo para su mantenimiento. Solo cuando la necesidad de implementaciones independientes se vuelve crucial, como al escalar el equipo de desarrollo, se debe considerar añadir una capa de red entre los módulos, los futuros microservicios.
## Lenguajes ricos en funciones
Nos emocionamos cuando se lanzan nuevas funciones en nuestro lenguaje favorito. Dedicamos tiempo a aprenderlas y desarrollamos código a partir de ellas.
Si hay muchas funciones, podemos pasar media hora probando algunas líneas de código para usar una u otra. Y es una pérdida de tiempo. Y lo que es peor, **cuando vuelvas más tarde, ¡tendrás que replantearte ese proceso!**
**No sólo tienes que entender este complicado programa, tienes que entender por qué un programador decidió que esta era la forma de abordar un problema a partir de las características disponibles.** `🤯`
Estas declaraciones las hace nada menos que Rob Pike.
> Reduce la carga cognitiva limitando el número de opciones.
Las características del lenguaje están bien, siempre que sean ortogonales entre sí.
<details>
<summary><b>Reflexiones de un ingeniero con 20 años de experiencia en C++ ⭐️</b></summary>
<br>
El otro día estaba mirando mi lector RSS y me di cuenta de que tengo unos trescientos artículos sin leer bajo la etiqueta "C++". No he leído ni un solo artículo sobre el lenguaje desde el verano pasado, ¡y me siento genial!<br><br>
Llevo 20 años usando C++, casi dos tercios de mi vida. Gran parte de mi experiencia reside en lidiar con los aspectos más oscuros del lenguaje (como comportamientos indefinidos de todo tipo). No es una experiencia reutilizable, y resulta un poco inquietante tirarlo todo ahora.<br><br>
Como podrás imaginar, el token <code>||</code> tiene un significado diferente en <code>requires ((!P<T> || !Q<T>))</code> y en <code>requires (!(P<T> || Q<T>))</code>. El primero es la disyunción de restricción, el segundo es el viejo operador lógico OR, y se comportan de manera diferente.<br><br>
No se puede asignar espacio para un tipo trivial y simplemente <code>memcpy</code> un conjunto de bytes sin esfuerzo adicional; eso no iniciará el ciclo de vida de un objeto. Esto ocurría antes de C++20. Se corrigió en C++20, pero la carga cognitiva del lenguaje solo ha aumentado.<br><br>
La carga cognitiva crece constantemente, incluso después de que se hayan corregido algunas cosas. Debería saber qué se corrigió, cuándo se corrigió y cómo era antes. Después de todo, soy un profesional. Claro, C++ es bueno en la compatibilidad con versiones anteriores, lo que también significa que <b>te enfrentarás</b> a ellas. Por ejemplo, el mes pasado un colega me preguntó sobre cierto comportamiento en C++03.<code>🤯</code><br><br>
Había 20 métodos de inicialización. Se había añadido una sintaxis de inicialización uniforme. Ahora tenemos 21 métodos de inicialización. Por cierto, ¿alguien recuerda las reglas para seleccionar constructores de la lista de inicializadores? Algo sobre la conversión implícita con la mínima pérdida de información, <i>pero si</i> el valor se conoce estáticamente, entonces...<code>🤯</code><br><br>
<b>Este aumento de la carga cognitiva no se debe a una tarea empresarial en cuestión. No es una complejidad intrínseca del dominio. Simplemente existe debido a razones históricas</b> (<i>carga cognitiva extraña</i>).<br><br>
Tuve que idear algunas reglas. Por ejemplo, si esa línea de código no es tan obvia y tengo que recordar el estándar, mejor no escribirla así. El estándar tiene unas 1500 páginas, por cierto.<br><br>
<b>De ninguna manera estoy tratando de culpar a C++.</b> Me encanta el lenguaje. Es solo que ahora estoy cansado.<br><br>
<p>Gracias a <a href="https://0xd34df00d.me" target="_blank">0xd34df00d</a> por el escrito.</p>
</details>
## Lógica de negocio y códigos de estado HTTP
En el backend, devolvemos:
`401` para token JWT vencido
`403` para acceso insuficiente
`418` para usuarios bloqueados
Los ingenieros del frontend usan la API del backend para implementar la funcionalidad de inicio de sesión. Tienen que crear temporalmente la siguiente carga cognitiva en sus cerebros:
`401` es para token JWT vencido // `🧠+`, ok solo recuérdalo temporalmente
`403` es para acceso insuficeinte // `🧠++`
`418` es para usuarios bloqueados // `🧠+++`
Los desarrolladores frontend introducirían (con suerte) algún tipo de diccionario «estado numérico -> significado» de su lado, de modo que las generaciones posteriores de colaboradores no tuvieran que recrear este mapeo en sus cerebros.
Entonces entran en juego los ingenieros de control de calidad:
"Oye, tengo el estado `403`, ¿ese token está vencido o no hay suficiente acceso?"
**Los ingenieros de control de calidad no pueden comenzar directamente con las pruebas, porque primero tienen que recrear la carga cognitiva que alguna vez crearon los ingenieros del backend.**
¿Por qué mantener esta asignación personalizada en nuestra memoria de trabajo? Es mejor abstraer los detalles de su negocio del protocolo de transferencia HTTP y devolver códigos autodescriptivos directamente en el cuerpo de la respuesta:
```json
{
"code": "jwt_ha_expirado"
}
```
Carga cognitiva en el frontend: `🧠` (genial, no se tienen en cuenta los hechos)
Carga cognitiva en el control de calidad: `🧠`
La misma regla se aplica a todo tipo de estados numéricos (en la base de datos o donde sea): **usa cadenas autodescriptivas.** No estamos en la era de las computadoras de 640K para optimizar la memoria.
> La gente pasa tiempo discutiendo entre el `401` y el `403`, tomando decisiones basadas en sus propios modelos mentales. Se incorporan nuevos desarrolladores y necesitan recrear ese proceso de pensamiento. Puede que hayas documentado los "porqués" (ADR) de tu código, ayudando a los recién llegados a comprender las decisiones tomadas. Pero al final, simplemente no tiene sentido. Podemos separar los errores en relacionados con el usuario o con el servidor, pero aparte de eso, la información es bastante confusa.
P.D.: A menudo resulta difícil distinguir entre "autenticación" y "autorización". Podemos usar términos más simples como ["inicio de sesión" y "permisos"](https://ntietz.com/blog/lets-say-instead-of-auth/) para reducir la carga cognitiva.
## Abusar del principio DRY
No repetirse es uno de los primeros principios que se enseñan como ingenieros de software. Está tan arraigado en nosotros que no soportamos unas pocas líneas de código adicionales. Aunque en general es una regla buena y fundamental, su uso excesivo genera una carga cognitiva que no podemos manejar.
Hoy en día, todos desarrollamos software basado en componentes lógicamente separados. A menudo, estos se distribuyen entre múltiples bases de código que representan servicios separados. Al esforzarnos por eliminar cualquier repetición, podemos terminar creando un acoplamiento estrecho entre componentes no relacionados. Como resultado, los cambios en una parte pueden tener consecuencias imprevistas en otras áreas aparentemente no relacionadas. También pueden dificultar la capacidad de reemplazar o modificar componentes individuales sin afectar a todo el sistema.`🤯`
De hecho, el mismo problema surge incluso dentro de un mismo módulo. Podrías extraer alguna funcionalidad común demasiado pronto, basándote en similitudes percibidas que podrían no existir a largo plazo. Esto puede generar abstracciones innecesarias difíciles de modificar o ampliar.
Rob Pike una vez dijo:
> Un poco de copia es mejor que un poco de dependencia.
Nos sentimos tentados a no reinventar la rueda con tanta fuerza que estamos dispuestos a importar bibliotecas grandes y pesadas para usar una función pequeña que podríamos escribir fácilmente nosotros mismos.
**Todas tus dependencias son tu código.** Recorrer más de 10 niveles de seguimiento de pila de alguna biblioteca importada y descubrir qué salió mal (*porque las cosas salen mal*) es doloroso.
## Acoplamiento estrecho con un marco
Hay mucha "magia" en los frameworks. Al depender demasiado de un framework, **obligamos a todos los futuros desarrolladores a aprender esa "magia" primero.** Puede llevar meses. Aunque los frameworks nos permiten lanzar MVP en cuestión de días, a la larga tienden a añadir complejidad y carga cognitiva innecesarias.
Peor aún, en algún momento, los frameworks pueden convertirse en una limitación importante al enfrentarse a un nuevo requisito que simplemente no se ajusta a la arquitectura. A partir de ahí, la gente termina bifurcando un framework y manteniendo su propia versión personalizada. Imagina la cantidad de carga cognitiva que un recién llegado tendría que desarrollar (es decir, aprender este framework personalizado) para aportar algún valor.`🤯`
**¡De ninguna manera abogamos por inventar todo desde cero!**
Podemos escribir código de forma relativamente independiente del framework. La lógica de negocio no debería residir en un framework, sino utilizar sus componentes. Sitúa el framework fuera de tu lógica principal. Úsalo como una biblioteca. Esto permitirá a los nuevos colaboradores aportar valor desde el primer día, sin necesidad de analizar primero la complejidad del framework.
> [Porqué odio los Frameworks](https://minds.md/benji/frameworks)
## Arquitectura en capas
Hay un cierto entusiasmo ingenieril en torno a todo esto.
Yo mismo fui un ferviente defensor de la arquitectura hexagonal/cebolla durante años. La usé ocasionalmente y animé a otros equipos a hacer lo mismo. La complejidad de nuestros proyectos aumentó; incluso el número de archivos se duplicó. Parecía que estábamos escribiendo mucho código de unión. Ante los requisitos en constante cambio, teníamos que realizar cambios en múltiples capas de abstracciones; todo se volvió tedioso. `🤯`
**Se supone que la abstracción oculta la complejidad, aquí solo agrega [indirección](https://fhur.me/posts/2024/thats-not-an-abstraction).** Pasar de una llamada a otra para analizar y determinar qué falla y qué falta es fundamental para resolver un problema rápidamente. Con el desacoplamiento de capas de esta arquitectura, se requiere un factor exponencial de trazas adicionales, a menudo inconexas, para llegar al punto donde se produce el fallo. Cada traza de este tipo ocupa espacio en nuestra limitada memoria de trabajo. `🤯`
Esta arquitectura parecía intuitiva al principio, pero cada vez que intentábamos aplicarla a nuestros proyectos, nos perjudicaba. Dedicamos años a actividades mentales innecesarias y a escribir código adhesivo inútil sin un valor comercial claro. Al contrario, empeoramos las cosas para el negocio al obligar a los recién llegados a aprender primero nuestros enfoques (modelos mentales). El tiempo de comercialización se ha reducido. Al final, lo abandonamos todo en favor del clásico principio de inversión de dependencias. **Sin términos de puerto/adaptador que aprender, sin capas innecesarias de abstracciones horizontales, sin carga cognitiva ajena.**
<details>
<summary><b>Principios y experiencia de programación</b></summary>
<div align="center">
<img src="img/complexity.png" alt="Código super simple" width="500">
</div>
<a href="https://twitter.com/flaviocopes">@flaviocopes</a>
</details>
Si crees que esta estratificación te permitirá reemplazar rápidamente una base de datos u otras dependencias, te equivocas. Cambiar el almacenamiento causa muchos problemas, y créenos, tener algunas abstracciones para la capa de acceso a datos es la menor de tus preocupaciones. En el mejor de los casos, las abstracciones pueden ahorrar aproximadamente un 10 % del tiempo de migración (si es que lo hay); el verdadero problema reside en las incompatibilidades del modelo de datos, los protocolos de comunicación, los desafíos de los sistemas distribuidos y las [interfaces implícitas](https://www.hyrumslaw.com).
> Con un número suficiente de usuarios de una API,
> no importa lo que prometas en el contrato:
> alguien dependerá de todos
> los comportamientos observables de tu sistema.
Realizamos una migración de almacenamiento que nos llevó unos 10 meses. El sistema anterior era de un solo subproceso, por lo que los eventos expuestos eran secuenciales. Todos nuestros sistemas dependían de ese comportamiento observado. Este comportamiento no formaba parte del contrato de la API ni se reflejaba en el código. Un nuevo almacenamiento distribuido no ofrecía esa garantía: los eventos se producían desordenados. Gracias a una abstracción, dedicamos solo unas horas a programar un nuevo adaptador de almacenamiento. **Dedicamos los siguientes 10 meses a gestionar eventos desordenados y otros desafíos.** Resulta curioso decir que las abstracciones nos ayudan a reemplazar componentes rápidamente.
**Entonces, ¿por qué pagar el precio de una alta carga cognitiva por una arquitectura de capas así, si no da resultados en el futuro?** Además, en la mayoría de los casos, ese futuro de reemplazar algún componente central nunca sucede.
Estas arquitecturas no son fundamentales, sino consecuencias subjetivas y sesgadas de principios más fundamentales. ¿Por qué confiar en esas interpretaciones subjetivas? Sigamos las reglas fundamentales: principio de inversión de dependencias, fuente única de verdad, carga cognitiva y ocultación de información. Su lógica de negocio no debería depender de módulos de bajo nivel como bases de datos, interfaz de usuario o frameworks. Deberíamos poder escribir pruebas para nuestra lógica central sin preocuparnos por la infraestructura, y listo. [Discusión](https://github.com/zakirullin/cognitive-load/discussions/24).
No añadas capas de abstracciones solo por el bien de la arquitectura. Añádelas siempre que necesites una extensión justificada por razones prácticas.
**[Las capas de abstracción no son gratuitas](https://blog.jooq.org/why-you-should-not-implement-layered-architecture), deben almacenarse en nuestra memoria de trabajo limitada.**
<div align="center">
<img src="/img/layers.png" alt="Layers" width="400">
</div>
## Diseño orientado al dominio
El diseño orientado al dominio tiene aspectos muy positivos, aunque a menudo se malinterpreta. Se suele decir: «Escribimos código en DDD», lo cual resulta un tanto extraño, ya que DDD se centra más en el espacio del problema que en el espacio de la solución.
El lenguaje ubicuo, el dominio, el contexto delimitado, la agregación y la tormenta de eventos se centran en el espacio del problema. Su objetivo es ayudarnos a comprender el dominio y a delimitar sus fronteras. El DDD permite que desarrolladores, expertos en el dominio y profesionales de negocio se comuniquen eficazmente mediante un lenguaje único y unificado. En lugar de centrarnos en estos aspectos del espacio del problema del DDD, solemos enfatizar estructuras de carpetas, servicios, repositorios y otras técnicas del espacio de soluciones.
Es probable que nuestra interpretación de DDD sea única y subjetiva. Y si basamos el código en esta comprensión, es decir, si generamos una carga cognitiva innecesaria, los futuros desarrolladores estarán condenados al fracaso. `🤯`
Las Topologías de Equipo ofrecen un marco mucho mejor y más fácil de entender que nos ayuda a distribuir la carga cognitiva entre los equipos. Los ingenieros tienden a desarrollar modelos mentales bastante similares tras aprender sobre las Topologías de Equipo. DDD, en cambio, parece crear diez modelos mentales distintos para diez lectores diferentes. En lugar de ser un terreno común, se convierte en un campo de batalla para debates innecesarios.
## Carga cognitiva en proyectos familiares
> El problema es que **familiaridad no es lo mismo que simplicidad.** Se *sienten* igual —esa misma facilidad para moverse por un espacio sin mucho esfuerzo mental— pero por razones muy distintas. Cada "truco ingenioso" (léase: "autocomplaciente") y poco convencional que uses supone un coste de aprendizaje para los demás. Una vez que lo hayan superado, les resultará más fácil trabajar con el código. Por eso es difícil reconocer cómo simplificar un código con el que ya estás familiarizado. ¡Por eso intento que "el recién llegado" critique el código antes de que se acostumbre demasiado a la rutina!
>
> Es probable que el autor o autores anteriores crearan este enorme desastre poco a poco, no de golpe. Así que eres la primera persona que ha tenido que intentar darle sentido a todo de una vez.
>
> En mi clase, un día describí un procedimiento de almacenado SQL muy extenso que estábamos analizando, con cientos de líneas de condicionales en una cláusula WHERE enorme. Alguien preguntó cómo era posible que llegara a ese extremo. Les respondí: "Cuando solo hay dos o tres condicionales, añadir uno más no supone ninguna diferencia. ¡Pero cuando hay veinte o treinta, añadir uno más tampoco supone ninguna diferencia!".
>
> No existe ninguna "fuerza simplificadora" que actúe sobre el código fuente, aparte de las decisiones deliberadas que tomes. Simplificar requiere esfuerzo, y la gente suele tener demasiada prisa.
>
> *Gracias a [Dan North](https://dannorth.net) por su comentario*.
Si has interiorizado los modelos mentales del proyecto en tu memoria a largo plazo, no experimentarás una alta carga cognitiva.
<div align="center">
<img src="/img/mentalmodelsv15.png" alt="Modelos mentales" width="700">
</div>
Cuantos más modelos mentales tenga que aprender un nuevo desarrollador, más tiempo tardará en aportar valor.
Una vez que incorpores a nuevos miembros a tu proyecto, intenta medir su nivel de confusión (la programación en parejas puede ser útil). Si permanecen confundidos durante más de 40 minutos seguidos, es probable que tengas aspectos que mejorar en tu código.
Si mantienes la carga cognitiva baja, los nuevos miembros del equipo podrán contribuir a tu código base en las primeras horas tras incorporarse a la empresa.
## Ejemplos
- Nuestra arquitectura es una arquitectura estándar de aplicación CRUD, [un monolito de Python sobre Postgres](https://danluu.com/simple-architectures/)
- Cómo Instagram escaló a 14 millones de usuarios con [solo 3 ingenieros](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)
- Las compañías donde nos quedábamos como ”wow, estos tipos son [súper inteligentes](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)” en su mayoría fracasaron
- Una función que conecta todo el sistema. Si quieres saber cómo funciona el sistema - [léelo](https://www.infoq.com/presentations/8-lines-code-refactoring)
- Diseño para la comprensibilidad: [El algoritmo de consenso Raft](https://www.youtube.com/watch?v=vYp4LYbnnW8)
Estas arquitecturas son un tanto aburridas y fáciles de entender. Cualquiera puede comprenderlas sin mucho esfuerzo mental.
Involucra a los desarrolladores junior en las revisiones de arquitectura; te ayudarán a identificar las áreas que requieren mayor esfuerzo mental.
> Los sistemas de software son quizás las cosas más intrincadas y complejas (en términos de número de tipos distintos de partes) que crea la humanidad.
>
> *Fred Brooks, El mítico hombre-mes*
**El mantenimiento del software es difícil**, las cosas fallan y necesitaríamos aprovechar al máximo el esfuerzo mental disponible. Cuantos menos componentes tenga el sistema, menos problemas habrá. La depuración también será menos exigente mentalmente.
> Depurar el código es el doble de difícil que escribirlo. Por lo tanto, si escribes el código de la forma más ingeniosa posible, por definición, no eres lo suficientemente inteligente como para depurarlo.
>
> *Brian Kernighan*
En general, la mentalidad de «¡Guau, esta arquitectura sí que sienta bien!» es engañosa. Se trata de una "sensación subjetiva puntual" que no dice nada sobre la realidad. Un enfoque mucho mejor es observar las consecuencias a largo plazo:
- ¿Es fácil reproducir y depurar un problema? ¿O hay que navegar entre las pilas de llamadas o los componentes distribuidos, intentando comprenderlo todo mentalmente?
- ¿Podemos realizar cambios rápidamente, o hay muchas incógnitas y la gente tiene miedo de tocar cosas?
- ¿Pueden los nuevos usuarios añadir funcionalidades rápidamente? ¿Hay modelos mentales específicos que aprender?
> ¿Qué son esos modelos mentales únicos? Se trata de un conjunto de reglas, generalmente una mezcla de DDD/CQRS/Arquitectura Limpia/Arquitectura Orientada a Eventos. Esta es una interpretación personal del autor sobre aquello que más le entusiasma. Sus propios modelos mentales subjetivos. **Carga cognitiva adicional que otros deben internalizar.**
Estas preguntas son mucho más difíciles de rastrear, y a menudo la gente prefiere no responderlas directamente. Fíjate en algunos de los sistemas de software más complejos del mundo, los que han resistido el paso del tiempo: Unix, Kubernetes, Chrome y Redis (ver comentarios más abajo). No encontrarás nada sofisticado en ellos; en general, son bastante simples, y eso es bueno.
## Conclusión
Imaginemos por un momento que lo que inferimos en el segundo capítulo no es cierto. Si ese es el caso, entonces la conclusión que acabamos de refutar, junto con las conclusiones del capítulo anterior que habíamos aceptado como válidas, podrían no ser correctas tampoco. `🤯`
¿Lo notas? No solo tienes que leer todo el artículo para entenderlo (¡módulos superficiales!), sino que el párrafo en general es difícil de comprender. Te hemos generado una carga cognitiva innecesaria. **No les hagas esto a tus compañeros.**
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="Autor inteligente" width="600">
</div>
Debemos reducir cualquier carga cognitiva que exceda lo que es intrínseco al trabajo que realizamos.
---
[LinkedIn](https://www.linkedin.com/in/zakirullin/), [X](https://twitter.com/zakirullin), [GitHub](https://github.com/zakirullin), artemzr(аt)g-yоu-knоw-com
<details>
<summary><b>Comentarios</b></summary>
<br>
<p><strong>Rob Pike</strong> <i>(Unix, Golang)</i><br>Nice article.</p>
<p><strong><a href="https://x.com/karpathy/status/1872038630405054853" target="_blank">Andrej Karpathy</a></strong> <i>(ChatGPT, Tesla)</i><br>Nice post on software engineering. Probably the most true, least practiced viewpoint.</p>
<p><strong><a href="https://x.com/elonmusk/status/1872346903792566655" target="_blank">Elon Musk</a></strong> <i>(Rockets)</i><br>True.</p>
<p><strong><a href="https://www.linkedin.com/feed/update/urn:li:activity:7277757844970520576/" target="_blank">Addy Osmani</a></strong> <i>(Chrome, the most complex software system in the world)</i><br>I've seen countless projects where smart developers created impressive architectures using the latest design patterns and microservices. But when new team members tried to make changes, they spent weeks just trying to understand how everything fits together. The cognitive load was so high that productivity plummeted and bugs multiplied.</p>
<p>The irony? Many of these complexity-inducing patterns were implemented in the name of "clean code."</p>
<p>What really matters is reducing unnecessary cognitive burden. Sometimes this means fewer, deeper modules instead of many shallow ones. Sometimes it means keeping related logic together instead of splitting it into tiny functions.</p>
<p>And sometimes it means choosing boring, straightforward solutions over clever ones. The best code isn't the most elegant or sophisticated - it's the code that future developers (including yourself) can understand quickly.</p>
<p>Your article really resonates with the challenges we face in browser development. You're absolutely right about modern browsers being among the most complex software systems. Managing that complexity in Chromium is a constant challenge that aligns perfectly with many of the points you made about cognitive load.</p>
<p>One way we try to handle this in Chromium is through careful component isolation and well-defined interfaces between subsystems (like rendering, networking, JavaScript execution, etc.). Similar to your deep modules example with Unix I/O - we aim for powerful functionality behind relatively simple interfaces. For instance, our rendering pipeline handles incredible complexity (layout, compositing, GPU acceleration) but developers can interact with it through clear abstraction layers.</p>
<p>Your points about avoiding unnecessary abstractions really hit home too. In browser development, we constantly balance between making the codebase approachable for new contributors while handling the inherent complexity of web standards and compatibility. </p>
<p>Sometimes the simplest solution is the best one, even in a complex system.</p>
<p><strong><a href="https://x.com/antirez" target="_blank">antirez</a></strong> <i>(Redis)</i><br>Totally agree about it :) Also, what I believe is missing from mentioned "A Philosophy of Software Design" is the concept of "design sacrifice". That is, sometimes you sacrifice something and get back simplicity, or performances, or both. I apply this idea continuously, but often is not understood.</p>
<p>A good example is the fact that I always refused to have hash items expires. This is a design sacrifice because if you have certain attributes only in the top-level items (the keys themselves), the design is simpler, values will just be objects. When Redis got hash expires, it was a nice feature but required (indeed) many changes to many parts, raising the complexity.</p>
<p>Another example is what I'm doing right now, Vector Sets, the new Redis data type. I decided that Redis would not be the source of truth about vectors, but that it can just take an approximate version of them, so I was able to do on-insert normalization, quantization without trying to retain the large floats vector on disk, and so forth. Many vector DBs don't sacrifice the fact of remembering what the user put inside (the full precision vector).</p>
<p>These are just two random examples, but I apply this idea everywhere. Now the thing is: of course one must sacrifice the right things. Often, there are 5% features that account for a very large amount of complexity: that is a good thing to kill :D</p>
<p><strong><a href="https://working-for-the-future.medium.com/about" target="_blank">A developer from the internet</a></strong><br>You would not hire me... I sell myself on my track record of released enterprise projects.</p>
<p>I worked with a guy that could speak design patterns. I could never speak that way, though I was one of the few that could well understand him. The managers loved him and he could dominate any development conversation. The people working around him said he left a trail of destruction behind him. I was told that I was the first person that could understand his projects. Maintainability matters. I care most about TCO (<i>Total Cost of Ownership</i>). For some firms, that's what matters.</p>
<p>I logged into Github after not being there for a while and for some reason it took me to an article in a repository by someone that seemed random. I was thinking "what is this" and had some trouble getting to my home page, so I read it. I didn't really register it at the time, but it was amazing. Every developer should read it. It largely said that almost everything we've been told about programming best practices leads to excessive "cognitive load", meaning our minds are getting kicked by the intellectual demands. I've known this for a while, especially with the demands of cloud, security and DevOps.</p>
<p>I also liked it because it described practices I have done for decades, but never much admit to because they are not popular... I write really complicated stuff and need all the help I can get.</p>
<p>Consider, if I'm right, it popped up because the Github folks, very smart people, though that developers should see it. I agree.</p>
<p><a href="https://news.ycombinator.com/item?id=45074248" target="_blank">Comments on Hacker News</a> (<a href="https://news.ycombinator.com/item?id=42489645" target="_blank">2</a>)</p>
</details>
================================================
FILE: README.ja.md
================================================
# 認知負荷こそが重要
[読みやすい版](https://minds.md/zakirullin/cognitive) | [中国語翻訳](https://github.com/zakirullin/cognitive-load/blob/main/README.zh-cn.md) | [韓国語翻訳](README.ko.md) | [トルコ語翻訳](README.tr.md) | [日本語翻訳](README.ja.md)
*これは生きた文書であり、最終更新は**2025年8月**です。あなたの貢献を歓迎します!*
## はじめに
世の中には多くのバズワードやベストプラクティスがありますが、そのほとんどがうまくいっていません。私たちにはもっと根本的な、誤りようのないものが必要です。
コードを読んでいて混乱を感じることがあります。混乱は時間とお金を消費します。混乱の原因は高い*認知負荷*にあります。これは決して抽象論ではなく、**人間の根本的制約**なのです。私たちはそれを実感できます。
コードを書くよりもコードを読んで理解することに費やす時間のほうがはるかに長いため、私たちは常に、過度の認知負荷をコードに埋め込んでいないかを自問すべきです。
## 認知負荷
> 認知負荷とは、開発者がタスクを完了するために考える必要がある量のことです。
コードを読むとき、変数の値、制御フローロジック、呼び出しシーケンスなどを頭に保持します。平均的な人はワーキングメモリに約[4つのチャンク](https://github.com/zakirullin/cognitive-load/issues/16)を保持できます。認知負荷がこの閾値を超えると、理解が一気に難しくなります。
> 完全に馴染みのないプロジェクトにバグ修正を依頼されたとしましょう。とても賢い開発者が貢献していたと聞かされました。多くの洗練されたアーキテクチャ、おしゃれなライブラリ、最新のテクノロジーが使われていました。つまり、**著者は私たちに高い認知負荷を作り出していた**のです。
<div align="center">
<img src="/img/cognitiveloadv6.png" alt="認知負荷" width="750">
</div>
私たちはプロジェクトの認知負荷を可能な限り減らすべきです。
<details>
<summary><b>認知負荷と中断</b></summary>
<img src="img/interruption.jpeg"><br>
</details>
## 認知負荷の種類
**内在的** - タスクの本来の難易度によって引き起こされます。これは削減できず、ソフトウェア開発の核心にあるものです。
**外在的** - 情報の提示方法によって作り出されます。タスクに直接関係しない要因、例えば賢い開発者のクセのようなもので引き起こされます。大幅に削減可能です。私たちはこの種の認知負荷に焦点を当てます。
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="内在的 vs 外在的" width="600">
</div>
外在的認知負荷の具体的で実践的な例にすぐに飛び込みましょう。
---
認知負荷のレベルを以下のように表記します:
`🧠`: 新鮮なワーキングメモリ、認知負荷ゼロ
`🧠++`: ワーキングメモリに2つの事実、認知負荷増加
`🤯`: 認知過負荷、4つ以上の事実
> 私たちの脳ははるかに複雑で未探索ですが、この単純化されたモデルで進めることができます。
## 複雑な条件文
```go
if val > someConstant // 🧠+
&& (condition2 || condition3) // 🧠+++、前の条件が真で、c2かc3のいずれかが真である必要性
&& (condition4 && !condition5) { // 🤯、この時点で理解が破綻しがち
...
}
```
意味のある名前を持つ中間変数を導入します:
```go
isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
// 🧠、記述的な変数があるので、条件を覚える必要がありません
if isValid && isAllowed && isSecure {
...
}
```
## ネストしたif文
```go
if isValid { // 🧠+、ネストしたコードは有効な入力にのみ適用
if isSecure { // 🧠++、有効で安全な入力にのみ処理
stuff // 🧠+++
}
}
```
早期リターンと比較してみましょう:
```go
if !isValid
return
if !isSecure
return
// 🧠、以前のリターンは気にしません。ここに到達したということは、すべてがOK
stuff // 🧠+
```
ハッピーパス(正常系)だけに集中でき、ワーキングメモリをあらゆる前提条件から解放します。
## 継承の悪夢
管理者ユーザーのためにいくつかの変更を依頼されました:`🧠`
`AdminController extends UserController extends GuestController extends BaseController`
ああ、機能の一部は`BaseController`にあります、見てみましょう:`🧠+`
基本的な役割メカニクスが`GuestController`で導入されました:`🧠++`
`UserController`で部分的に変更されました:`🧠+++`
ついにここに来ました、`AdminController`、コーディングしましょう!`🧠++++`
ああ、待って、`AdminController`を継承する`SuperuserController`があります。`AdminController`を変更することで継承クラスで問題が起こる可能性があるので、まず`SuperuserController`を調べましょう:`🤯`
継承よりコンポジション(委譲)を好みましょう。詳細は割愛します。 - 参考資料は[こちら](https://www.youtube.com/watch?v=hxGOiiR9ZKg)が充実しています。
## 小さなメソッド、クラス、モジュールが多すぎる
> この文脈では、メソッド、クラス、モジュールは交換可能です
「メソッドは15行未満であるべき」や「クラスは小さくあるべき」といった格言は、やや間違っていることが判明しました。
**深いモジュール** - シンプルなインターフェース、複雑な機能
**浅いモジュール** - インターフェースが提供する小さな機能に対して相対的に複雑
<div align="center">
<img src="/img/deepmodulev8.png" alt="深いモジュール" width="700">
</div>
浅いモジュールが多すぎると、プロジェクトを理解するのが困難になる可能性があります。**各モジュールの責任を頭に置くだけでなく、それらすべてのインタラクションも把握する必要があります**。浅いモジュールの目的を理解するために、まずすべての関連モジュールの機能を調べる必要があります。そのような浅いコンポーネント間をジャンプするのは精神的に疲れます。人間は<a target="_blank" href="https://blog.separateconcerns.com/2023-09-11-linear-code.html">線形に読む</a>のが自然です。
> 情報隠蔽は最重要であり、浅いモジュールでは複雑性をそれほど隠せません。
私には2つのペットプロジェクトがあり、どちらも約5,000行のコードです。最初のプロジェクトには80の浅いクラスがあり、2番目のプロジェクトには7つの深いクラスしかありませんでした。これらのプロジェクトのメンテナンスを1年半していませんでした。
戻ってきたとき、最初のプロジェクトの80のクラス間のすべてのインタラクションを解きほぐすのが非常に困難であることに気づきました。コーディングを始める前に、膨大な認知負荷を再構築する必要がありました。一方、2番目のプロジェクトは、シンプルなインターフェースを持つ少数の深いクラスしかなかったため、すぐに把握できました。
> 最高のコンポーネントは、強力な機能を提供しながらシンプルなインターフェースを持つものです。
> **John K. Ousterhout**
UNIX I/Oのインターフェースは非常にシンプルです。基本的な呼び出しは5つだけです:
```python
open(path, flags, permissions)
read(fd, buffer, count)
write(fd, buffer, count)
lseek(fd, offset, referencePosition)
close(fd)
```
このインターフェースの現代的な実装は**数十万行のコード**を持ちます。多くの複雑性が内部に隠蔽されています。しかし、シンプルなインターフェースのおかげで使いやすいのです。
> この深いモジュールの例は、John K. Ousterhoutの[A Philosophy of Software Design](https://web.stanford.edu/~ouster/cgi-bin/book.php)から取られました。この本はソフトウェア開発における複雑性の本質をカバーするだけでなく、Parnasの影響力のある論文[On the Criteria To Be Used in Decomposing Systems into Modules](https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf)の最高の解釈も提供します。どちらも必読です。関連読み物:[A Philosophy of Software Design vs Clean Code](https://github.com/johnousterhout/aposd-vs-clean-code)、[It's probably time to stop recommending Clean Code](https://qntm.org/clean)、[Small Functions considered Harmful](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29)。
P.S. 責任が多すぎる肥大化したGodオブジェクト(神オブジェクト)を支持していると思われるなら、それは間違いです。
## 一つのことに責任を持つ
あまりにもしばしば、曖昧な「モジュールは一つの、そして唯一の一つのことに責任を持つべき」原則に従って、多くの浅いモジュールを作ることになります。このぼんやりとした「一つのこと」とは何でしょうか?オブジェクトをインスタンス化することは一つのことでしょうか?だから[MetricsProviderFactoryFactory](https://minds.md/benji/frameworks)は問題ないように見えます。**そのようなクラスの名前とインターフェースは、実装全体よりも精神的に負荷が大きくなる傾向があります。それはどのような抽象化でしょうか?** 何かが間違っています。
私たちはユーザーや利害関係者を満足させるためにシステムを変更します。私たちは彼らに対して責任があります。
> モジュールは一つの、そして唯一の一つのユーザーまたは利害関係者に責任を持つべきです。
これが単一責任原則の本質です。簡単に言えば、一箇所にバグを導入して、2人の異なるビジネス関係者が苦情を言いに来た場合、原則に違反しています。モジュールで行うことの数とは関係ありません。
しかし今でも、この規則は害を与える可能性があります。この原則は個人の数だけ異なる方法で理解される可能性があります。より良いアプローチは、それがどのくらいの認知負荷を作り出すかを見ることです。一箇所での変更が異なるビジネス領域間で連鎖反応を引き起こす可能性があることを覚えておくのは、精神的に負荷が大きいです。それだけのことで、学ぶべき難しい用語はありません。
## 浅いマイクロサービスが多すぎる
この浅い-深いモジュール原則はスケールに依存せず、マイクロサービスアーキテクチャにも適用できます。浅いマイクロサービスが多すぎるのは良くありません - 業界は多少「マクロサービス」、つまりそれほど浅くない(=深い)サービスに向かっています。最悪で修正が困難な現象の一つは、いわゆる分散モノリスで、これはしばしばこの過度に粒度の細かい浅い分離の結果です。
かつて、5人の開発者チームが17個(!)のマイクロサービスを導入したスタートアップをコンサルティングしました。彼らは10ヶ月スケジュールが遅れていて、パブリックリリースには程遠い状態でした。新しい要件のたびに4つ以上のマイクロサービスでの変更が必要でした。そのような分散システムで問題を再現しデバッグするには膨大な時間がかかりました。市場投入時間と認知負荷の両方が許容できないほど高かったのです。`🤯`
これは新しいシステムの不確実性に対する正しいアプローチでしょうか?最初に正しい論理的境界を引き出すことは非常に困難です。重要なのは、情報が最も多い時点まで責任を持って待てるだけ決定を遅らせることです。最初にネットワーク層を導入することで、最初から設計決定を元に戻すのを困難にしています。チームの唯一の正当化は:「FAANG企業がマイクロサービスアーキテクチャが効果的であることを証明した」でした。**現実に立ち返りましょう。**
[Tanenbaum-Torvalds討論](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate)では、Linuxのモノリシック設計が欠陥があり時代遅れであり、代わりにマイクロカーネルアーキテクチャを使うべきだと主張されました。確かに、マイクロカーネル設計は「理論的で美的な」観点から優れているように見えました。実用的な側面では――30年経った今、マイクロカーネルベースのGNU Hurdはまだ開発中で、モノリシックなLinuxが至る所で使われています。このページはLinuxで動いており、あなたのスマートティーポットもLinuxで動いています。モノリシックなLinuxで。
本当に分離されたモジュールを持つ良く作られたモノリスは、多くのマイクロサービスよりもはるかに柔軟であることが多いです。また、維持するのに必要な認知的努力もはるかに少なくなります。開発チームのスケーリングなど、別々のデプロイメントの必要性が重要になった場合にのみ、モジュール間、将来のマイクロサービス間にネットワーク層を追加することを検討すべきです。
## 機能豊富な言語
お気に入りの言語で新機能がリリースされるとワクワクします。これらの機能を学ぶのに時間を費やし、その上にコードを構築します。
多くの機能がある場合、数行のコードを書くのに30分を費やして、ある機能を使うか別の機能を使うかを考えるかもしれません。それは時間の無駄です。しかし、もっと悪いことに、**後で戻ってきたときに、その思考プロセスを再現する必要があります!**
**この複雑なプログラムを理解するだけでなく、利用可能な機能から問題にアプローチするこの方法をプログラマーがなぜ決定したのかを理解する必要があります。** `🤯`
これらの発言は他ならぬRob Pikeによるものです。
> 選択肢の数を制限して認知負荷を減らす。
言語機能は、お互いに直交している限り問題ありません。
<details>
<summary><b>20年のC++経験を持つエンジニアからの考え ⭐️</b></summary>
<br>
先日RSSリーダーを見ていると、「C++」タグの下に未読記事が約300個あることに気づきました。昨年の夏以降、この言語についての記事を一つも読んでおらず、気分は良好です!<br><br>
私は20年間C++を使っており、それは人生の約3分の2です。私の経験の大部分は、言語の最も暗い部分(あらゆる種類の未定義動作など)を扱うことにあります。それは再利用可能な経験ではなく、今それをすべて捨てるのはちょっと不気味です。<br><br>
例えば、トークン<code>||</code>が<code>requires ((!P<T> || !Q<T>))</code>と<code>requires (!(P<T> || Q<T>))</code>で異なる意味を持つことを想像できますか。最初は制約分離、2番目は昔ながらの論理OR演算子で、これらは異なる動作をします。<br><br>
自明な型のためのスペースを割り当て、そこに一連のバイトを<code>memcpy</code>するだけでは、追加の努力なしにはオブジェクトの生存期間を開始できません。これはC++20以前の場合でした。C++20で修正されましたが、言語の認知負荷は増加しただけです。<br><br>
物事が修正されても認知負荷は常に増加しています。私はプロとして、何が修正されたか、いつ修正されたか、以前はどうだったかを知っている必要があります。確かに、C++はレガシーサポートが得意で、それはあなたが<b>そのレガシーに直面する</b>ことも意味します。例えば、先月同僚がC++03でのある動作について尋ねました。<code>🤯</code><br><br>
初期化の方法は20通りありました。統一初期化構文が追加されました。今は21通りの初期化方法があります。ところで、初期化リストからコンストラクタを選択する規則を覚えていますか?情報の損失が最小の暗黙変換についてのもので、<i>しかし値が静的に分かっている場合は</i>、それから... <code>🤯</code><br><br>
<b>この増加した認知負荷は、手元のビジネスタスクによるものではありません。それはドメインの本質的な複雑性ではありません。歴史的な理由で存在しているだけです</b>(<i>外在的認知負荷</i>)。<br><br>
私はいくつかのルールを決める必要がありました。例えば、そのコード行が明らかでなく、標準を覚えなければならない場合は、そのように書かないほうが良いです。ちなみに、標準は約1500ページの長さです。<br><br>
<b>決してC++を非難しようとしているのではありません。</b>私はこの言語を愛しています。ただ、今は疲れているのです。<br><br>
<p><a href="https://0xd34df00d.me" target="_blank">0xd34df00d</a>に執筆していただき、ありがとうございます。</p>
</details>
## ビジネスロジックとHTTPステータスコード
バックエンドで以下を返します:
`401` 期限切れJWTトークンの場合
`403` アクセス権限不足の場合
`418` 利用停止中のユーザーの場合
フロントエンドのエンジニアは、バックエンドAPIを使用してログイン機能を実装します。彼らは一時的に以下の認知負荷を頭の中に保持しなければなりません:
`401`は期限切れJWTトークンの場合 // `🧠+`、一時的に覚えておく
`403`はアクセス権限不足の場合 // `🧠++`
`418`は利用停止中のユーザーの場合 // `🧠+++`
フロントエンド開発者は(願わくば)彼らの側で何らかの`数値ステータス → 意味`辞書を導入し、後続の貢献者世代がこのマッピングを頭の中で再構築する必要がないようにするでしょう。
それからQAエンジニアが登場します:
「やあ、`403`ステータスを受け取ったけど、これは期限切れトークンなの、それともアクセス権限不足なの?」
**QAエンジニアは、バックエンドのエンジニアがかつて作成した認知負荷を再構築しなければならないので、すぐにテストに飛び込むことができません。**
なぜこのカスタムマッピングをワーキングメモリに保持するのでしょうか?ビジネス詳細をHTTP転送プロトコルから抽象化し、レスポンスボディに自己記述的なコードを直接返すほうが良いです:
```json
{
"code": "jwt_has_expired"
}
```
フロントエンド側の認知負荷:`🧠`(新鮮、頭に保持される事実なし)
QA側の認知負荷:`🧠`
同じ規則があらゆる数値ステータス(データベースやその他の場所)に適用されます - **自己記述的な文字列を好みましょう**。メモリを最適化するための640Kコンピューターの時代ではありません。
> 人々は`401`と`403`の間で議論し、自分の心的モデルに基づいて決定を下すのに時間を費やします。新しい開発者が入ってきて、その思考プロセスを再構築する必要があります。コードの「なぜ」(ADR)を文書化して、新参者が下された決定を理解できるようにしているかもしれません。しかし結局のところ、それは全く意味をなしません。エラーをユーザー関連またはサーバー関連に分離することはできますが、それ以外は物事がぼやけています。
P.S. 「認証」と「認可」を区別するのはしばしば精神的に負荷が大きいです。認知負荷を減らすために、[「ログイン」と「権限」](https://ntietz.com/blog/lets-say-instead-of-auth/)のようなより簡単な用語を使うことができます。
## DRY原則の乱用
Don't repeat yourself(自分を繰り返すな) - これはソフトウェアエンジニアとして教わる最初の原則の一つです。それは私たち自身に深く埋め込まれており、数行の余分なコードの存在に耐えられません。一般的には良い基本的な規則ですが、使いすぎると私たちが処理できない認知負荷につながります。
今日では、誰もが論理的に分離されたコンポーネントに基づいてソフトウェアを構築しています。多くの場合、これらは別々のサービスを表す複数のコードベースに分散されています。あらゆる繰り返しを排除しようと努力すると、関連のないコンポーネント間で密結合を作り出すことになるかもしれません。結果として、一部の変更は他の一見関係のない領域で予期しない結果をもたらす可能性があります。また、システム全体に影響を与えることなく個々のコンポーネントを置き換えたり変更したりする能力を妨げることもあります。`🤯`
実際、同じ問題は単一のモジュール内でも発生します。長期的に実際には存在しないかもしれない認識された類似性に基づいて、共通機能を早すぎる段階で抽出するかもしれません。これは変更や拡張が困難な不要な抽象化をもたらす可能性があります。
Rob Pikeはかつて言いました:
> 少しの依存よりも少しのコピーのほうが良い。
車輪を再発明しないことに強く惑わされ、私たち自身で簡単に書けるような小さな関数を使うために大きくて重いライブラリをインポートしがちです。
**すべての依存関係は実質的に「あなたのコード」です。** インポートされたライブラリの10以上のレベルのスタックトレースを通して何が悪かったかを把握する(*物事は悪化するから*)のは苦痛です。
## フレームワークとの密結合
フレームワークには多くの「魔法」があります。フレームワークに過度に依存することで、**すべての今後の開発者にその「魔法」を最初に学ばせることを強制します**。それには数ヶ月かかることがあります。フレームワークによって数日でMVPを起動できますが、長期的にはそれらは不要な複雑性と認知負荷を追加する傾向があります。
さらに悪いことに、ある時点でフレームワークは、アーキテクチャに合わない新しい要件に直面したときに重大な制約となる可能性があります。ここから人々はフレームワークをフォークして独自のカスタム版を維持することになります。新参者が価値を提供するためにどのくらいの認知負荷を構築(つまり、このカスタムフレームワークを学習)する必要があるか想像してみてください。`🤯`
**決してすべてをゼロから発明することを提唱しているのではありません!**
私たちは多少フレームワークに依存しない方法でコードを書くことができます。ビジネスロジックはフレームワーク内に存在すべきではありません。むしろ、フレームワークのコンポーネントを使用すべきです。フレームワークをコアロジックの外側に置きます。フレームワークをライブラリのような方法で使用します。これにより、新しい貢献者はまずフレームワーク関連の複雑性の層をかいくぐる必要なしに、初日から価値を追加できるようになります。
> [Why I Hate Frameworks](https://minds.md/benji/frameworks)
## レイヤードアーキテクチャ
こうしたエンジニアリングのすべてに、ある種の興奮があります。
私自身、何年もヘキサゴナル/オニオンアーキテクチャの情熱的な提唱者でした。あちこちで使用し、他のチームにも勧めました。プロジェクトの複雑性が上がり、ファイル数だけでも倍増しました。多くのグルーコードを書いているように感じました。絶え間なく変化する要件に対して、複数の抽象化レイヤーにわたって変更を行う必要があり、結局それはすべて面倒になりました。`🤯`
抽象化は複雑性を隠すことになっていますが、ここではただ[間接性](https://fhur.me/posts/2024/thats-not-an-abstraction)を追加しているだけです。何が悪くて何が欠けているかを把握するために、呼び出しから呼び出しへとジャンプしなければならないのは、問題を迅速に解決する上で重要でありながら、負担が大きいです。このアーキテクチャのレイヤー分離では、障害が発生するポイントにたどり着くために余計な手間が指数関数的に増え、トレースも断片的になりがちです。そのようなトレースはそれぞれ、私たちの限られたワーキングメモリのスペースを占有します。`🤯`
このアーキテクチャは最初は直感的に理にかなっているように見えましたが、プロジェクトに適用しようとするたびに、良いことよりもはるかに害をなしました。最終的に、古き良き依存関係逆転原則(DIP)を支持して、すべてを諦めました。**学ぶべきポートやアダプター、不要な水平抽象化、外在的認知負荷、それらは必要ありません**
<details>
<summary><b>コーディング原則と経験</b></summary>
<img src="img/complexity.png"><br>
<a href="https://twitter.com/flaviocopes">@flaviocopes</a>
</details>
そのような階層化によってデータベースや他の依存関係を迅速に置き換えることができると思うなら、それは間違いです。ストレージを変更すると多くの問題が発生し、信じてください、データアクセス層にいくつかの抽象化を持つことは最小の心配事です。せいぜい、抽象化は移行時間の約10%(もしあれば)を節約できますが、実際の痛みはデータモデルの非互換性、通信プロトコル、分散システムの課題、そして[暗黙のインターフェース](https://www.hyrumslaw.com)にあります。
> APIの十分なユーザー数があれば、
> 契約で何を約束しようと関係ありません:
> システムのすべての観察可能な動作が
> 誰かによって依存されます。
私たちはストレージ移行を行い、約10ヶ月かかりました。古いシステムはシングルスレッドだったので、公開されるイベントは順次でした。私たちのすべてのシステムがその観察された動作に依存していました。この動作はAPIコントラクトの一部ではなく、コードに反映されていませんでした。新しい分散ストレージにはその保証がありませんでした - イベントは順不同で来ました。抽象化のおかげで、新しいストレージアダプターのコーディングには数時間しかかかりませんでした。**順不同イベントやその他の課題への対処に次の10ヶ月を費やしました。** 抽象化がコンポーネントを迅速に置き換える助けになるというのは今では面白いことです。
**では、そのようなレイヤードアーキテクチャの高い認知負荷の代価を支払うのはなぜでしょうか、それが将来報われないのであれば?** さらに、ほとんどの場合、いくつかのコアコンポーネントを置き換えるその未来は決して起こりません。
これらのアーキテクチャは基本的ではなく、より基本的な原則の主観的で偏った結果にすぎません。なぜそれらの主観的な解釈に依存するのでしょうか?代わりに基本的な規則に従いましょう:依存関係逆転原則、単一の真実の源、認知負荷、情報隠蔽。私たちのビジネスロジックは、データベース、UI、フレームワークなどの低レベルモジュールに依存すべきではありません。インフラストラクチャを心配することなくコアロジックのテストを書けるべきで、それだけです。[議論](https://github.com/zakirullin/cognitive-load/discussions/24)。
アーキテクチャのために抽象化レイヤーを追加してはいけません。実用的な理由で正当化される拡張ポイントが必要なときにそれらを追加しましょう。
**[抽象化レイヤーは無料ではありません](https://blog.jooq.org/why-you-should-not-implement-layered-architecture)、それらは私たちの限られたワーキングメモリに保持される必要があります**。
<div align="center">
<img src="/img/layers.png" alt="レイヤー" width="400">
</div>
## ドメイン駆動設計
ドメイン駆動設計にはいくつかの素晴らしいポイントがありますが、しばしば誤解されています。人々は「私たちはDDDでコードを書く」と言いますが、これは少し奇妙です。なぜならDDDはソリューション空間よりも問題空間に関するものだからです。
ユビキタス言語、ドメイン、境界づけられたコンテキスト、集約、イベントストーミングはすべて問題空間に関するものです。それらはドメインについての洞察を学び、境界を抽出するのを助けることを意図しています。DDDは開発者、ドメインエキスパート、ビジネス関係者が単一の統一された言語を使って効果的にコミュニケーションを取ることを可能にします。DDDのこれらの問題空間の側面に焦点を当てる代わりに、私たちは特定のフォルダ構造、サービス、リポジトリ、その他のソリューション空間技術を強調する傾向があります。
私たちがDDDを解釈する方法が独特で主観的である可能性があります。そして、この理解の上にコードを構築する場合、つまり、多くの外在的認知負荷を作り出す場合 - 将来の開発者は運命づけられています。`🤯`
Team Topologiesは、認知負荷をチーム間で分割するのに役立つはるかに良い、理解しやすいフレームワークを提供します。エンジニアはTeam Topologiesについて学んだ後、多少似たような心的モデルを開発する傾向があります。一方、DDDは10人の異なる読者に対して10の異なる心的モデルを作り出すように見えます。共通の基盤になる代わりに、不要な議論の戦場になります。
## 馴染みのあるプロジェクトでの認知負荷
> 問題は、**馴染みやすさが単純さと同じではない**ことです。それらは同じように*感じます* - 同じような精神的努力なしに空間を移動するその同じ容易さ - しかし非常に異なる理由によるものです。あなたが使用するすべての「賢い」(読む:「自己満足的な」)で非慣用的なトリックは、他のすべての人に学習ペナルティを課します。彼らがその学習を行うと、コードを扱うのが困難でなくなります。だから、すでに馴染みのあるコードを簡潔にする方法を認識するのは困難です。これが私が「新人」にあまりにも制度化される前にコードを批評してもらうようにする理由です!
>
> 前の著者がこの巨大な混乱を一度にではなく、一つの小さな増分ずつ作り出した可能性があります。だから、あなたが一度にすべてを理解しようとする最初の人なのです。
>
> 私のクラスでは、ある日見ていた広がりのあるSQL格納プロシージャを説明します。巨大なWHERE句に何百行の条件文がありました。誰かがどうしてこんなにひどくなるのを許したのかと尋ねました。私は彼らに言いました:「2つや3つの条件しかないときに、もう一つ追加してもどんな違いも生みません。20や30の条件があるときに、もう一つ追加してもどんな違いも生みません!」
>
> あなたが行う意図的な選択以外に、コードベースに作用する「簡素化力」はありません。簡素化には努力が必要で、人々はあまりにもしばしば急いでいます。
>
> *[Dan North](https://dannorth.net)のコメントに感謝*。
プロジェクトの心的モデルを長期記憶に内在化していれば、高い認知負荷を経験することはないでしょう。
<div align="center">
<img src="/img/mentalmodelsv15.png" alt="心的モデル" width="700">
</div>
学ぶべき心的モデルが多いほど、新しい開発者が価値を提供するのに時間がかかります。
プロジェクトに新しい人をオンボードするときは、彼らが持つ混乱の量を測定してみてください(ペアプログラミングが役立つかもしれません)。40分以上連続して混乱している場合 - コードで改善すべきことがあります。
認知負荷を低く保てば、人々は会社に参加してから最初の数時間以内にコードベースに貢献できます。
## 例
- 私たちのアーキテクチャは標準的なCRUDアプリアーキテクチャ、[PostgreSQLの上のPythonモノリス](https://danluu.com/simple-architectures/)
- Instagramが[わずか3人のエンジニア](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)で1400万ユーザーまでスケールした方法
- 私たちが「うわあ、この人たちは[めちゃくちゃ賢い](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)」と思った企業の大部分は失敗した
- システム全体を配線する一つの関数。システムがどのように動作するかを知りたいなら - [それを読みに行く](https://www.infoq.com/presentations/8-lines-code-refactoring)
これらのアーキテクチャは非常に退屈で理解しやすいです。誰でも大きな精神的努力なしにそれらを把握できます。
アーキテクチャレビューにジュニア開発者を関与させましょう。彼らは精神的に負荷の大きい領域を特定するのに役立ちます。
**ソフトウェアの維持は困難です**、物事は壊れ、節約できるすべての精神的努力が必要になります。
## 結論
ちょっと想像してみてください。もし第2章で推論したことが実は正しくなかったとしたらどうなるでしょう? その場合、いま否定した結論だけでなく、これまで正しいと受け入れてきた前章の結論までも、正しくないかもしれません。🤯
実感できますか? 意味を理解するために記事のあちこちを行き来しなければならず(=浅いモジュール!)、しかも、全体としてとても分かりにくい段落になっています。私たちは、あなたの頭に余計な認知負荷をつくり出してしまったのです。**同僚にこのようなことをしてはいけません。**
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="賢い著者" width="600">
</div>
私たちが行う仕事の本質的なものを超えるあらゆる認知負荷を減らすべきです。
---
[LinkedIn](https://www.linkedin.com/in/zakirullin/)、[X](https://twitter.com/zakirullin)、[GitHub](https://github.com/zakirullin)
[読みやすい版](https://minds.md/zakirullin/cognitive)
<details>
<summary><b>コメント</b></summary>
<br>
<p><strong>Rob Pike</strong><br>良い記事です。</p>
<p><strong><a href="https://x.com/karpathy/status/1872038630405054853" target="_blank">Andrej Karpathy</a></strong> <i>(ChatGPT、Tesla)</i><br>ソフトウェアエンジニアリングに関する良い投稿。おそらく最も真実で、最も実践されていない観点。</p>
<p><strong><a href="https://x.com/elonmusk/status/1872346903792566655" target="_blank">Elon Musk</a></strong><br>真実だ。</p>
<p><strong><a href="https://www.linkedin.com/feed/update/urn:li:activity:7277757844970520576/" target="_blank">Addy Osmani</a></strong> <i>(Chrome、世界で最も複雑なソフトウェアシステム)</i><br>賢い開発者が最新の設計パターンとマイクロサービスを使って印象的なアーキテクチャを作成した無数のプロジェクトを見てきました。しかし、新しいチームメンバーが変更を加えようとしたとき、すべてがどのように組み合わさるかを理解するのに何週間も費やしました。認知負荷がとても高く、生産性が急落し、バグが倍増しました。</p>
<p>皮肉なことに?これらの複雑性を誘発するパターンの多くは「クリーンコード」の名の下に実装されていました。</p>
<p>本当に重要なのは、不要な認知負荷を減らすことです。時には多くの浅いモジュールではなく、より少ない深いモジュールを意味することもあります。時には小さな関数に分割するのではなく、関連するロジックを一緒に保つことを意味することもあります。</p>
<p>そして時には賢いものよりも退屈で素直な解決策を選ぶことを意味します。最高のコードは最もエレガントで洗練されたものではありません - 将来の開発者(あなた自身を含む)が迅速に理解できるコードです。</p>
<p>あなたの記事は、ブラウザ開発で私たちが直面する課題と本当に共鳴します。認知負荷について言った多くのポイントと完璧に一致する、現代のブラウザが最も複雑なソフトウェアシステムの一つであることについて、あなたは絶対に正しいです。</p>
<p>Chromiumでこれを処理しようとする一つの方法は、慎重なコンポーネント分離とサブシステム間(レンダリング、ネットワーキング、JavaScript実行など)の明確に定義されたインターフェースです。Unix I/Oでの深いモジュールの例と似ています - 比較的シンプルなインターフェースの背後にある強力な機能を目指しています。例えば、私たちのレンダリングパイプラインは信じられないほどの複雑性(レイアウト、合成、GPUアクセラレーション)を処理しますが、開発者は明確な抽象化レイヤーを通してそれと相互作用できます。</p>
<p>不要な抽象化を避けることについてのあなたのポイントも本当に心に響きます。ブラウザ開発では、新しい貢献者にとってコードベースをアプローチしやすくすることとウェブ標準と互換性の本質的な複雑性を処理することの間で常にバランスを取っています。</p>
<p>時には、複雑なシステムであっても、最も単純な解決策が最良の解決策です。</p>
<p><strong><a href="https://x.com/antirez" target="_blank">antirez</a></strong> <i>(Redis)</i><br>それについて完全に同意します :) また、私が信じているのは、言及された「A Philosophy of Software Design」から欠けているのは「設計犠牲」の概念です。つまり、時には何かを犠牲にして、シンプルさ、またはパフォーマンス、またはその両方を取り戻すのです。私はこのアイデアを継続的に適用していますが、しばしば理解されません。</p>
<p>良い例は、私がハッシュアイテムの期限切れを持つことを常に拒否したことです。これは設計犠牲です。なぜなら、特定の属性をトップレベルアイテム(キー自体)にのみ持つ場合、設計はよりシンプルになり、値は単なるオブジェクトになります。Redisがハッシュ期限切れを得たとき、それは良い機能でしたが、実際に多くの部分に多くの変更を必要とし、複雑性を高めました。</p>
<p>別の例は、私が今やっていること、Vector Sets、新しいRedisデータタイプです。私はRedisがベクトルについての真実の源ではなく、それらの近似版を取ることができるだけであると決定したので、ディスク上の大きなフロートベクトルを保持しようとすることなく、挿入時の正規化、量子化を行うことができました。多くのベクトルDBは、ユーザーが入れたもの(完全精度ベクトル)を覚えているという事実を犠牲にしません。</p>
<p>これらは単なる2つのランダムな例ですが、このアイデアをどこでも適用しています。今のことは:もちろん正しいものを犠牲にしなければなりません。しばしば、非常に大きな複雑性を占める5%の機能があります:それを殺すのは良いことです :D</p>
<p><strong><a href="https://working-for-the-future.medium.com/about" target="_blank">インターネットからの開発者</a></strong><br>あなたは私を雇わないでしょう... 私はリリースされたエンタープライズプロジェクトの実績で自分を売り込みます。</p>
<p>設計パターンを話すことができる人と働いたことがあります。私はそのように話すことは決してできませんでしたが、彼を理解できる数少ない人の一人でした。マネージャーは彼を愛し、彼はあらゆる開発会話を支配することができました。彼の周りで働く人々は、彼が後ろに破壊の跡を残していると言いました。私は彼のプロジェクトを理解できる最初の人だと言われました。メンテナビリティは重要です。私は最もTCOを気にします。いくつかの企業にとって、それが重要なことです。</p>
<p>しばらくGithubに行っていなかった後にログインし、なぜか無作為に見える誰かによるリポジトリ内の記事に連れて行かれました。「これは何だ」と思って、ホームページにたどり着くのに少し問題があったので、それを読みました。その時は実際には登録していませんでしたが、それは素晴らしいものでした。すべての開発者が読むべきです。それは基本的に、プログラミングのベストプラクティスについて私たちが言われてきたことのほぼすべてが過度の「認知負荷」につながると言っていました。つまり、私たちの心が知的要求によって蹴られているということです。私はしばらくの間これを知っていました、特にクラウド、セキュリティ、DevOpsの要求で。</p>
<p>また、それが何十年もやってきた実践について説明していたので気に入りました、しかし人気がないためにあまり認めることはありません... 私は本当に複雑なものを書き、すべてのヘルプが得られる必要があります。</p>
<p>考えてみると、私が正しければ、それはGithubの人々、非常に賢い人々が開発者がそれを見るべきだと思ったために現れました。私も同意します。</p>
<p><a href="https://news.ycombinator.com/item?id=45074248" target="_blank">Hacker Newsのコメント</a> (<a href="https://news.ycombinator.com/item?id=42489645" target="_blank">2</a>)</p>
</details>
================================================
FILE: README.ko.md
================================================
# 인지 부하가 중요합니다
## 소개 (Introduction)
세상에는 수많은 유행어와 모범 사례가 있지만, 대부분은 실패했습니다. 우리에게는 더 근본적인 것, 틀릴 수 없는 무언가가 필요합니다.
때때로 우리는 코드를 살펴보면서 혼란을 느낍니다. 혼란은 시간과 비용을 초래합니다. 혼란은 높은 *인지 부하*로 인해 발생합니다. 높은 인지 부하는 화려하고 추상적인 개념이 아니라 **근본적인 인간의 제약**입니다. 상상 속의 존재가 아니라 실제로 존재하며 우리가 느낄 수 있는 것입니다.
우리는 코드를 작성하는 시간보다 읽고 이해하는 데 훨씬 더 많은 시간을 소비하므로, 코드에 과도한 인지 부하를 심고 있지는 않은지 끊임없이 자문해야 합니다.
## 인지 부하 (Cognitive load)
> 인지 부하란 개발자가 작업을 완료하기 위해 생각해야 하는 양입니다.
우리는 코드를 읽을 때 변수 값, 제어 흐름 논리, 호출 순서와 같은 것들을 머릿속에 넣습니다. 평균적인 사람은 작업 기억에 대략 [4개의 덩어리](https://github.com/zakirullin/cognitive-load/issues/16)를 담을 수 있습니다. 인지 부하가 작업 기억의 임계값에 도달하면 내용을 이해하기가 훨씬 더 어려워집니다.
*완전히 낯선 프로젝트의 수정 요청을 받았다고 가정해 봅시다. 정말 똑똑한 개발자가 이 프로젝트에 기여했다고 들었습니다. 수많은 멋진 아키텍처, 화려한 라이브러리, 유행하는 기술이 사용되었습니다. 다시 말해, **작성자가 우리에게 높은 인지 부하를 안겨준 것입니다.***
<div align="center">
<img src="/img/cognitiveloadv6.png" alt="인지 부하" width="750">
</div>
우리는 프로젝트에서 인지 부하를 최대한 줄여야 합니다.
<details>
<summary><b>인지 부하와 방해 요소</b></summary>
<img src="img/interruption.jpeg"><br>
</details>
## 인지 부하의 유형 (Types of cognitive load)
**내재적 인지 부하** - 작업 자체의 고유한 어려움으로 인해 발생합니다. 줄일 수 없으며, 소프트웨어 개발의 핵심에 있습니다.
**외재적 인지 부하** - 정보가 제시되는 방식으로 인해 발생합니다. 똑똑한 작성자의 기행과 같이 작업과 직접 관련 없는 요인으로 인해 발생합니다. 크게 줄일 수 있습니다. 이 글에서는 이 외재적 인지 부하에 초점을 맞출 것입니다.
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="내재적 대 외재적" width="600">
</div>
외재적 인지 부하의 구체적인 실제 사례로 바로 넘어가 보겠습니다.
---
인지 부하 수준은 다음과 같이 표현합니다:
`🧠`: 새로운 작업 기억, 인지
부하 없음
`🧠++`: 작업 기억에 두 가지
사실, 인지 부하 증가
`🤯`: 인지 과부하, 4가지 이상의 사실
> 인간의 뇌는 훨씬 더 복잡하고 미지의 영역이지만, 이 글에서는 이 단순화된 모델을 사용합니다.
## 복잡한 조건문 (Complex conditionals)
```go
if val > someConstant // 🧠+
&& (condition2 || condition3) // 🧠+++, 이전 조건은 참이어야 하고, c2 또는 c3 중 하나는 참이어야 함
&& (condition4 && !condition5) { // 🤯, 이 시점에서 우리는 혼란에 빠짐
...
}
```
의미 있는 이름을 가진 중간 변수를 도입합니다.
```go
isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
// 🧠, 조건을 기억할 필요가 없음, 설명적인 변수가 있음
if isValid && isAllowed && isSecure {
...
}
```
## 중첩된 if 문 (Nested ifs)
```go
if isValid { // 🧠+, 유효한 입력에만 중첩된 코드 적용
if isSecure { // 🧠++, 유효하고 안전한 입력에 대해서만 작업 수행
stuff // 🧠+++
}
}
```
조기 반환과 비교해 보세요.
```go
if !isValid
return
if !isSecure
return
// 🧠, 이전 반환은 신경 쓰지 않음, 여기까지 왔다면 모든 것이 좋음
stuff // 🧠+
```
우리는 행복한 경로에만 집중할 수 있으므로, 온갖 종류의 전제 조건으로부터 작업 기억을 해방시킬 수 있습니다.
## 상속의 악몽 (Inheritance nightmare)
관리자 사용자를 위해 몇 가지 사항을 변경해 달라는 요청을 받았습니다: `🧠`
`AdminController extends UserController extends GuestController extends BaseController`
아, 기능의 일부가 `BaseController`에 있군요, 한번 봅시다: `🧠+`
기본적인 역할 메커니즘은 `GuestController`에 도입되었습니다: `🧠++`
`UserController`에서 일부 내용이 변경되었습니다: `🧠+++`
드디어 `AdminController`입니다, 코딩합시다! `🧠++++`
아, 잠깐, `AdminController`를 확장하는 `SuperuserController`가 있습니다. `AdminController`를 수정하면 상속된 클래스에서 문제가 발생할 수 있으므로, 먼저 우리는 `SuperuserController`를 살펴봐야 합니다: `🤯`
상속보다는 합성을 선호하세요. 자세한 내용은 다루지 않겠습니다. 이미 [많은 자료](https://www.youtube.com/watch?v=hxGOiiR9ZKg)가 있습니다.
## 너무 많은 작은 메서드, 클래스 또는 모듈 (Too many small methods, classes or modules)
> 이 맥락에서 메서드, 클래스, 모듈은 서로 바꿔 사용할 수 있습니다.
"메서드는 15줄 미만이어야 한다" 또는 "클래스는 작아야 한다"와 같은 만트라는 다소 잘못된 것으로 밝혀졌습니다.
**깊은 모듈** - 간단한 인터페이스, 복잡한 기능
**얕은 모듈** - 인터페이스가 제공하는 작은 기능에 비해 상대적으로 복잡함
<div align="center">
<img src="/img/deepmodulev8.png" alt="깊은 모듈" width="700">
</div>
얕은 모듈이 너무 많으면 프로젝트를 이해하기 어려울 수 있습니다. **각 모듈의 책임뿐만 아니라 모든 상호 작용까지 염두에 두어야 합니다.** 얕은 모듈의 목적을 이해하려면 먼저 관련된 모든 모듈의 기능을 살펴봐야 합니다. `🤯`
> 정보 은닉은 가장 중요하며, 얕은 모듈에서는 복잡성을 그다지 많이 숨기지 않습니다.
필자는 두 개의 개인 프로젝트를 가지고 있는데, 둘 다 약 5,000줄의 코드입니다. 첫 번째 프로젝트에는 80개의 얕은 클래스가 있고, 두 번째 프로젝트에는 7개의 깊은 클래스만 있습니다. 1년 반 동안 이 두 프로젝트를 유지보수하지 않았습니다.
돌아와서 보니 첫 번째 프로젝트에서 80개 클래스 간의 모든 상호 작용을 푸는 것이 극도로 어렵다는 것을 깨달았습니다. 코딩을 시작하기 전에 엄청난 양의 인지 부하를 다시 구축해야 했습니다. 반면에 두 번째 프로젝트는 간단한 인터페이스를 가진 몇 개의 깊은 클래스만 있었기 때문에 빠르게 파악할 수 있었습니다.
> 최고의 구성 요소는 강력한 기능을 제공하면서도 간단한 인터페이스를 가진 것입니다.
> **존 K. 아우스터하우트**
유닉스 I/O의 인터페이스는 매우 간단합니다. 다섯 가지 기본 호출만 있습니다.
```python
open(path, flags, permissions)
read(fd, buffer, count)
write(fd, buffer, count)
lseek(fd, offset, referencePosition)
close(fd)
```
이 인터페이스의 최신 구현에는 **수십만 줄의 코드**가 있습니다. 많은 복잡성이 내부적으로 숨겨져 있습니다. 그러나 간단한 인터페이스 덕분에 사용하기 쉽습니다.
> 이 깊은 모듈 예제는 존 K. 아우스터하우트의 저서 [소프트웨어 설계 철학](https://web.stanford.edu/~ouster/cgi-bin/book.php)에서 가져왔습니다. 이 책은 소프트웨어 개발의 복잡성의 본질을 다룰 뿐만 아니라, 파나스의 영향력 있는 논문 [모듈로 시스템을 분해하는 데 사용될 기준에 관하여](https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf)에 대한 가장 훌륭한 해석을 담고 있습니다. 둘 다 필독서입니다. 기타 관련 자료: [소프트웨어 설계 철학 대 클린 코드](https://github.com/johnousterhout/aposd-vs-clean-code), [클린 코드를 추천하는 것을 멈춰야 할 때일지도 모릅니다](https://qntm.org/clean), [작은 함수는 해롭다고 여겨집니다](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29).
P.S. 만약 이 글이 너무 많은 책임을 가진 비대한 갓 오브젝트를 지지한다고 생각한다면, 잘못된 생각입니다.
## 한 가지 책임 (Responsible for one thing)
너무나 자주, 우리는 "모듈은 한 가지, 오직 한 가지 일에만 책임져야 한다"는 모호한 원칙에 따라 수많은 얕은 모듈을 만들게 됩니다. 이 흐릿한 한 가지란 무엇일까요? 객체를 인스턴스화하는 것은 한 가지 일이죠, 그렇죠? 그러니 [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks)는 괜찮아 보입니다. 그런 클래스의 이름과 인터페이스는 전체 구현보다 정신적으로 더 부담스러운데, 그게 무슨 추상화일까요? 뭔가 잘못되었습니다.
> 그런 얕은 구성 요소 사이를 오가는 것은 정신적으로 지치며, [선형적 사고](https://blog.separateconcerns.com/2023-09-11-linear-code.html)가 우리 인간에게 더 자연스럽습니다.
우리는 사용자와 이해 관계자를 만족시키기 위해 시스템을 변경합니다. 우리는 그들에게 책임이 있습니다.
> 모듈은 한 명, 오직 한 명의 사용자 또는 이해 관계자에게만 책임져야 합니다.
이것이 바로 단일 책임 원칙의 전부입니다. 간단히 말해, 한 곳에서 버그를 발생시켰는데 두 명의 다른 비즈니스 담당자가 불평하러 온다면, 우리는 원칙을 위반한 것입니다. 이는 모듈에서 수행하는 작업의 수와는 아무런 관련이 없습니다.
하지만 지금도 이 규칙은 득보다 실이 많을 수 있습니다. 이 원칙은 개인의 수만큼이나 다양한 방식으로 이해될 수 있습니다. 더 나은 접근 방식은 이러한 원칙 적용이 얼마나 많은 인지 부하를 발생시키는지 살펴보는 것입니다. 한 곳에서의 변경이 여러 다른 비즈니스 흐름에 걸쳐 연쇄 반응을 일으킬 수 있다는 것을 기억하는 것은 정신적으로 부담스럽습니다. 그리고 그것이 핵심입니다. 배울 만한 화려한 용어는 없습니다.
## 너무 많은 얕은 마이크로서비스 (Too many shallow microservices)
이 얕고 깊은 모듈 원칙은 규모에 구애받지 않으며, 마이크로서비스 아키텍처에 적용할 수 있습니다. 너무 많은 얕은 마이크로서비스는 아무런 도움이 되지 않습니다. 업계는 다소 "매크로서비스", 즉 그렇게 얕지 않은(=깊은) 서비스로 향하고 있습니다. 가장 최악이고 수정하기 어려운 현상 중 하나는 소위 분산 모놀리스인데, 이는 종종 이러한 지나치게 세분화된 얕은 분리의 결과입니다.
필자는 한때 5명의 개발자로 구성된 팀이 17(!)개의 마이크로서비스를 도입한 스타트업에 컨설팅을 한 적이 있습니다. 이 팀은 예정보다 10개월 늦었고 공개 시점에 가까워 보이지 않았습니다. 새로운 요구 사항이 있을때마다 4개 이상의 마이크로서비스의 변경으로 이어졌습니다. 통합된 영역에서의 진단 난이도는 급증했습니다. 공개 시점과 인지 부하 모두 용납할 수 없을 정도로 높았습니다. `🤯`
이러한 접근 방식이 새로운 시스템의 불확실성에 접근하는 올바른 방법일까요? 처음부터 올바른 논리적 경계를 도출하는 것은 엄청나게 어렵습니다. 핵심은 책임감 있게 기다릴 수 있는 한 결정을 최대한 늦추는 것입니다. 왜냐하면 그때가 결정을 내리는 데 필요한 가장 많은 정보를 가지고 있기 때문입니다. 처음부터 네트워크 계층을 도입함으로써 우리는 설계 결정을 처음부터 되돌리기 어렵게 만듭니다. 팀의 유일한 정당화는 "FAANG 기업들이 마이크로서비스 아키텍처가 효과적이라는 것을 증명했다"는 것이었습니다. *이봐요, 큰 꿈은 그만 꾸세요.*
[타넨바움-토발즈 논쟁](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate)은 리눅스의 모놀리식 설계가 결함이 있고 구식이며, 마이크로커널 아키텍처를 사용해야 한다고 주장했습니다. 실제로 마이크로커널 설계는 "이론적이고 미적인" 관점에서 우월해 보였습니다. 실용적인 측면에서는 30년이 지난 지금도 마이크로커널 기반 GNU Hurd는 여전히 개발 중이며, 모놀리식 리눅스는 어디에나 있습니다. 이 페이지는 리눅스로 구동되며, 여러분의 스마트 주전자도 리눅스로 구동됩니다. 모놀리식 리눅스로 말이죠.
진정으로 격리된 모듈을 갖춘 잘 만들어진 모놀리스는 종종 여러 마이크로서비스보다 훨씬 유연합니다. 또한 유지 관리에 훨씬 적은 인지적 노력이 필요합니다. 개발팀 확장과 같이 별도의 배포 필요성이 중요해질 때만 모듈, 즉 미래의 마이크로서비스 사이에 네트워크 계층을 추가하는 것을 고려해야 합니다.
## 기능이 풍부한 언어 (Feature-rich languages)
우리는 좋아하는 언어에 새로운 기능이 출시될 때 흥분을 느낍니다. 우리는 이러한 기능을 배우는 데 시간을 보내고, 새로운 기능을 기반으로 코드를 작성합니다.
기능이 많으면 한두 가지 기능을 사용하기 위해 몇 줄의 코드를 가지고 30분을 보낼 수도 있습니다. 그리고 그러한 시간 소모는 낭비입니다. 하지만 더 나쁜 것은 **나중에 돌아왔을 때 그 사고 과정을 다시 만들어야 한다는 것입니다!**
**이 복잡한 프로그램을 이해해야 할 뿐만 아니라, 프로그래머가 사용 가능한 기능 중에서 왜 이런 방식으로 문제에 접근하기로 결정했는지 이해해야 합니다.** `🤯`
이런 주장은 다름 아닌 롭 파이크가 한 것입니다.
> 선택의 수를 제한하여 인지 부하를 줄이십시오.
언어 기능은 서로 직교하는 한 괜찮습니다.
<details>
<summary><b>C++ 경력 20년 엔지니어의 생각 ⭐️</b></summary>
<br>
며칠 전 RSS 리더를 보다가 "C++" 태그 아래에 읽지 않은 기사가 300개 정도 있다는 것을 알았습니다. 지난 여름 이후로 언어에 대한 기사를 단 한 편도 읽지 않았는데, 기분이 아주 좋습니다!<br><br>
저는 지금까지 20년 동안 C++를 사용해 왔는데, 이는 제 인생의 거의 3분의 2에 해당합니다. 제 경험의 대부분은 언어의 가장 어두운 구석(예: 온갖 종류의 정의되지 않은 동작)을 다루는 데 있습니다. 재사용 가능한 경험이 아니며, 지금 모든 것을 버리는 것은 좀 소름 끼치는 일입니다.<br><br>
상상해 보세요. <code>||</code> 토큰은 <code>requires ((!P<T> || !Q<T>))</code>와 <code>requires (!(P<T> || Q<T>))</code>에서 다른 의미를 갖습니다. 첫 번째는 제약 조건 논리합이고, 두 번째는 예전의 논리 OR 연산자이며, 다르게 동작합니다.<br><br>
단순한 유형에 대한 공간을 할당하고 거기에 바이트 집합을 <code>memcpy</code>하는 것만으로는 추가적인 노력 없이 객체의 수명을 시작할 수 없습니다. 이것은 C++20 이전의 경우였습니다. C++20에서 수정되었지만 언어의 인지 부하는 증가했을 뿐입니다.<br><br>
문제가 해결되었음에도 불구하고 인지 부하는 지속적으로 증가하고 있습니다. 무엇이 수정되었는지, 언제 수정되었는지, 이전에는 어땠는지 알아야 합니다. 저는 결국 전문가입니다. 물론 C++는 레거시 지원이 훌륭하며, 이는 또한 여러분이 그 레거시에 <b>직면하게 될 것</b>을 의미합니다. 예를 들어, 지난달 동료가 C++03의 일부 동작에 대해 저에게 물었습니다. <code>🤯</code><br><br>
초기화 방법에는 20가지가 있었습니다. 균일 초기화 구문이 추가되었습니다. 이제 21가지 초기화 방법이 있습니다. 그런데 초기화 목록에서 생성자를 선택하는 규칙을 기억하는 사람이 있습니까? 정보 손실이 가장 적은 암시적 변환에 관한 것이지만, <i>만약</i> 값이 정적으로 알려져 있다면... <code>🤯</code><br><br>
<b>이렇게 증가된 인지 부하는 당면한 비즈니스 작업으로 인해 발생하는 것이 아닙니다. 도메인의 본질적인 복잡성이 아닙니다. 단지 역사적인 이유로 존재하는 것입니다</b>(<i>외재적 인지 부하</i>).<br><br>
몇 가지 규칙을 만들어야 했습니다. 예를 들어, 코드 줄이 명확하지 않고 표준을 기억해야 한다면 그렇게 작성하지 않는 것이 좋습니다. 참고로 표준은 약 1500페이지입니다.<br><br>
<b>결코 C++를 비난하려는 것이 아닙니다.</b> 저는 이 언어를 사랑합니다. 단지 지금은 지쳤을 뿐입니다.<br><br>
<p>
<a href="https://0xd34df00d.me" target="_blank\">0xd34df00d</a>님 작성 감사합니다.
</p>
</details>
## 비즈니스 로직과 HTTP 상태 코드 (Business logic and HTTP status codes)
백엔드에서는 다음을 반환합니다.
`401` 만료된 JWT 토큰
`403` 접근 권한 부족
`418` 차단된 사용자
프론트엔드 엔지니어는 백엔드 API를 사용하여 로그인 기능을 구현합니다. 그들은 일시적으로 다음과 같은 인지 부하를 뇌에 만들어야 합니다:
`401`은 만료된 JWT 토큰입니다 // `🧠+`, 그냥 일시적으로 기억하세요
`403`은 접근 권한 부족입니다 // `🧠++`
`418`은 차단된 사용자입니다 // `🧠+++`
프론트엔드 개발자는 (바라건대) `숫자 상태 -> 의미` 사전을 만들어 후속 기여자들이 이 매핑을 뇌에서 다시 만들 필요가 없도록 할 것입니다.
그런 다음 QA 엔지니어가 등장합니다.
\"이봐요, `403` 상태를 받았는데, 토큰이 만료된 건가요, 아니면 접근 권한이 부족한 건가요?\"
**QA 엔지니어는 백엔드 엔지니어가 한때 만들었던 인지 부하를 먼저 다시 만들어야 하기 때문에 바로 테스트를 시작할 수 없습니다.**
왜 이러한 사용자 지정 매핑을 작업 기억에 담아두어야 할까요? 비즈니스 세부 정보를 HTTP 전송 프로토콜에서 추상화하고 응답 본문에 직접 자체 설명 코드를 반환하는 것이 좋습니다.
```json
{
"code": "jwt_has_expired"
}
```
프론트엔드 측의 인지 부하: `🧠` (새로움, 마음에 담아둔 사실 없음)
QA 측의 인지 부하: `🧠`
데이터베이스나 어디에서든 모든 종류의 숫자 상태에 동일한 규칙이 적용됩니다. **자체 설명 문자열을 선호하세요.** 현대 개발 환경은 메모리를 최적화하기 위해 640K 컴퓨터 시대에 살고 있지 않습니다.
> 사람들은 `401`과 `403` 사이에서 논쟁하며 자신의 멘탈 모델을 기반으로 결정을 내립니다. 새로운 개발자가 들어오고 새로운 개발자는 그 사고 과정을 다시 만들어야 합니다. 코드에 대한 "이유"(ADR)를 문서화하여 새로운 사람이 내린 결정을 이해하도록 도울 수 있습니다. 하지만 결국에는 아무 의미가 없습니다. 오류를 사용자 관련 또는 서버 관련으로 분리할 수 있지만, 그 외에는 상황이 다소 모호합니다.
추신: "인증"과 "권한 부여"를 구별하는 것은 종종 정신적으로 부담스럽습니다. 인지 부하를 줄이기 위해 ["로그인" 및 "권한"](https://ntietz.com/blog/lets-say-instead-of-auth/)과 같은 더 간단한 용어를 사용할 수 있습니다.
## DRY 원칙 남용 (Abusing DRY principle)
반복하지 마십시오(Do not repeat yourself) - DRY 원칙은 소프트웨어 엔지니어로서 배우는 첫 번째 원칙 중 하나입니다. 우리 자신에게 너무 깊이 내재되어 있어서 몇 줄의 추가 코드를 참을 수 없습니다. 일반적으로 좋고 근본적인 규칙이지만, 과도하게 사용하면 감당할 수 없는 인지 부하로 이어집니다.
요즘 모든 사람은 논리적으로 분리된 구성 요소를 기반으로 소프트웨어를 구축합니다. 종종 이러한 구성 요소는 별도의 서비스를 나타내는 여러 코드베이스에 분산되어 있습니다. 모든 반복을 제거하려고 노력하면 관련 없는 구성 요소 간에 긴밀한 결합이 발생할 수 있습니다. 결과적으로 한 부분의 변경이 다른 관련 없어 보이는 영역에 의도하지 않은 결과를 초래할 수 있습니다. 또한 전체 시스템에 영향을 주지 않고 개별 구성 요소를 교체하거나 수정하는 기능을 방해할 수 있습니다. `🤯`
사실, 동일한 문제는 단일 모듈 내에서도 발생합니다. 장기적으로 실제로 존재하지 않을 수 있는 인식된 유사성을 기반으로 공통 기능을 너무 일찍 추출할 수 있습니다. 이로 인해 수정하거나 확장하기 어려운 불필요한 추상화가 발생할 수 있습니다.
롭 파이크는 이렇게 말했습니다.
> 약간의 복사본이 약간의 종속성보다 낫습니다.
우리는 바퀴를 재발명하지 않으려는 유혹이 너무 강해서, 스스로 쉽게 작성할 수 있는 작은 함수를 사용하기 위해 크고 무거운 라이브러리를 가져올 준비가 되어 있습니다.
**모든 종속성은 여러분의 코드입니다.** 가져온 라이브러리의 10개 이상의 스택 추적 수준을 살펴보고 무엇이 잘못되었는지 알아내는 것(*왜냐하면 문제가 발생하기 때문입니다*)은 고통스럽습니다.
## 프레임워크와의 긴밀한 결합 (Tight coupling with frameworks)
프레임워크에는 많은 "마법"이 있습니다. 프레임워크에 너무 많이 의존함으로써 **프로젝트는 다가올 모든 개발자에게 그 "마법"을 먼저 배우도록 강요합니다.** 몇 달이 걸릴 수 있습니다. 프레임워크를 사용하면 며칠 만에 MVP를 출시할 수 있지만, 장기적으로는 불필요한 복잡성과 인지 부하를 추가하는 경향이 있습니다.
더 나쁜 것은, 어느 시점에서 프레임워크가 아키텍처에 맞지 않는 새로운 요구 사항에 직면했을 때 심각한 제약이 될 수 있다는 것입니다. 이 시점부터 사람들은 프레임워크를 포크하고 자체 사용자 지정 버전을 유지 관리하게 됩니다. 새로운 사람이 가치를 제공하기 위해 구축해야 하는 인지 부하의 양(즉, 이 사용자 지정 프레임워크를 배우는 것)을 상상해 보십시오. `🤯`
**결코 모든 것을 처음부터 발명하라고 주장하는 것이 아닙니다!**
우리는 다소 프레임워크에 구애받지 않는 방식으로 코드를 작성할 수 있습니다. 비즈니스 로직은 프레임워크 내에 있어서는 안 되며, 오히려 프레임워크의 구성 요소를 사용해야 합니다. 프레임워크를 핵심 로직 외부에 두십시오. 프레임워크를 라이브러리처럼 사용하십시오. 이러한 접근 방식은 새로운 기여자가 프레임워크 관련 복잡성의 잔해를 거칠 필요 없이 첫날부터 가치를 추가할 수 있습니다.
> [내가 프레임워크를 싫어하는 이유](https://minds.md/benji/frameworks)
## 계층형 아키텍처 (Layered architecture)
이런 기술적인 주제엔 엔지니어로서의 확실한 설렘이 있다 (certain engineering excitement).
필자도 수년 동안 Hexagonal/Onion 아키텍처의 열렬한 지지자였습니다. 여기저기서 사용했고 다른 팀에도 그렇게 하도록 권장했습니다. 필자가 참여한 프로젝트의 복잡성은 증가했고, 파일 수만 해도 두 배가 되었습니다. 마치 많은 접착 코드를 작성하는 것처럼 느껴졌습니다. 끊임없이 변화하는 요구 사항에 따라 여러 추상화 계층에 걸쳐 변경해야 했고, 모든 것이 지루해졌습니다. `🤯`
추상화는 복잡성을 숨기기 위한 것이지만, 여기서는 단지 [간접성](https://fhur.me/posts/2024/thats-not-an-abstraction)을 추가할 뿐입니다. 호출에서 호출로 이동하여 읽고 무엇이 잘못되었고 무엇이 누락되었는지 파악하는 것은 문제를 신속하게 해결하기 위한 필수 요구 사항입니다. 이 아키텍처의 계층 분리는 실패가 발생하는 지점에 도달하기 위해 기하급수적으로 많은, 종종 분리된 추적을 필요로 합니다. 이러한 각 추적은 제한된 작업 기억 공간을 차지합니다. `🤯`
이 아키텍처는 처음에는 직관적으로 이해가 되었지만, 프로젝트에 적용하려고 할 때마다 득보다 실이 훨씬 많았습니다. 결국 우리는 오래된 의존성 역전 원칙을 위해 모든 것을 포기했습니다. **배울 포트/어댑터 용어도 없고, 불필요한 수평적 추상화 계층도 없고, 외재적 인지 부하도 없습니다.**
<details>
<summary><b>코딩 원칙과 경험</b></summary>
<img src="img/complexity.png"><br>
<a href="https://twitter.com/flaviocopes\">@flaviocopes</a>
</details>
이러한 계층화가 데이터베이스나 다른 종속성을 신속하게 교체할 수 있게 해줄 것이라고 생각한다면 착각입니다. 스토리지를 변경하면 많은 문제가 발생하며, 데이터 액세스 계층에 대한 일부 추상화가 있다는 것은 걱정거리 중 가장 작은 부분이라는 것이 분명합니다. 기껏해야 추상화는 마이그레이션 시간의 약 10%를 절약할 수 있지만(있다면), 진짜 고통은 데이터 모델 비호환성, 통신 프로토콜, 분산 시스템 문제 및 [암시적 인터페이스](https://www.hyrumslaw.com)에 있습니다.
> API 사용자가 충분히 많으면,
> 계약서에 무엇을 약속하든 상관없습니다.
> 시스템의 모든 관찰 가능한 동작은
> 누군가에게 의존하게 될 것입니다.
한 프로젝트에서는 스토리지 마이그레이션을 했고, 약 10개월이 걸렸습니다. 이전 시스템은 단일 스레드였기 때문에 노출된 이벤트는 순차적이었습니다. 모든 시스템이 관찰된 해당 동작에 의존했습니다. 이 동작은 API 계약의 일부가 아니었고 코드에 반영되지 않았습니다. 새로운 분산 스토리지는 그러한 보장이 없었습니다. 이벤트가 순서 없이 발생했습니다. 추상화 덕분에 새로운 스토리지 어댑터를 코딩하는 데 몇 시간밖에 걸리지 않았습니다. **다음 10개월은 순서 없는 이벤트 및 기타 문제를 처리하는 데 보냈습니다.** 이제 추상화가 구성 요소를 신속하게 교체하는 데 도움이 된다고 말하는 것은 우스꽝스럽습니다.
**그렇다면 미래에 성과를 거두지 못한다면 왜 그러한 계층형 아키텍처에 대해 높은 인지 부하라는 대가를 치러야 할까요?** 게다가 대부분의 경우 일부 핵심 구성 요소를 교체하는 미래는 결코 오지 않습니다.
이러한 아키텍처는 근본적인 것이 아니라 더 근본적인 원칙의 주관적이고 편향된 결과일 뿐입니다. 왜 그러한 주관적인 해석에 의존해야 할까요? 대신 근본적인 규칙을 따르십시오: 의존성 역전 원칙, 단일 진실 공급원, 인지 부하 및 정보 은닉. 비즈니스 로직은 데이터베이스, UI 또는 프레임워크와 같은 하위 수준 모듈에 의존해서는 안 됩니다. 인프라에 대해 걱정하지 않고 핵심 로직에 대한 테스트를 작성할 수 있어야 하며, 그것이 핵심입니다. [토론](https://github.com/zakirullin/cognitive-load/discussions/24).
아키텍처를 위해 추상화 계층을 추가하지 마십시오. 실용적인 이유로 정당화되는 확장 지점이 필요할 때마다 추가하십시오.
**[추상화 계층은 공짜가 아닙니다](https://blog.jooq.org/why-you-should-not-implement-layered-architecture). 제한된 작업 기억에 담아두어야 합니다.**
<div align="center">
<img src="/img/layers.png" alt="계층" width="400">
</div>
## 도메인 주도 설계 (Domain-Driven Design)
도메인 주도 설계(Domain-driven design)는 분명 훌륭한 개념들이 많지만, 종종 오해를 받기도 한다.
사람들은 '우리는 DDD 방식으로 코드를 짠다'고 말하곤 하는데, 이는 다소 어색한 표현이다.
왜냐하면 DDD는 '해결 방법(solution space)'이 아니라 '문제 영역(problem space)'에 대한 접근 방식이기 때문이다.
유비쿼터스 언어, 도메인, 경계 컨텍스트, 집계, 이벤트 스토밍은 모두 '문제 영역(problem space)'에 관한 것입니다. 도메인에 대한 통찰력을 배우고 경계를 추출하는 데 도움이 되도록 만들어졌습니다. DDD를 통해 개발자, 도메인 전문가 및 비즈니스 담당자는 단일하고 통일된 언어를 사용하여 효과적으로 의사소통할 수 있습니다. DDD의 이러한 '문제 영역(problem space)' 측면에 초점을 맞추는 대신 특정 폴더 구조, 서비스, 리포지토리 및 기타 '해결 방법(solution space)' 기술을 강조하는 경향이 있습니다.
각자가 DDD를 해석하는 방식은 독특하고 주관적일 가능성이 높습니다. 그리고 이러한 이해를 바탕으로 코드를 작성한다면, 즉 많은 외재적 인지 부하를 만든다면 미래의 개발자는 파멸할 것입니다. `🤯`
팀 토폴로지는 팀 전체에 걸쳐 인지 부하를 분산하는 데 도움이 되는 훨씬 더 좋고 이해하기 쉬운 프레임워크를 제공합니다. 엔지니어는 팀 토폴로지에 대해 배운 후 다소 유사한 멘탈 모델을 개발하는 경향이 있습니다. 반면에 DDD는 10명의 다른 독자에게 10개의 다른 멘탈 모델을 만드는 것처럼 보입니다. 공통 기반이 되는 대신 불필요한 논쟁의 장이 됩니다.
## 예시 (Examples)
- 우리 아키텍처는 표준 CRUD 앱 아키텍처, [Postgres 기반 Python 모놀리스](https://danluu.com/simple-architectures/)입니다.
- 인스타그램이 [단 3명의 엔지니어](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)로 1,400만 사용자까지 확장한 방법
- 우리가 "와, 이 사람들은 [정말 똑똑하네](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)"라고 생각했던 회사들은 대부분 실패했습니다.
- 전체 시스템을 연결하는 하나의 함수. 시스템이 어떻게 작동하는지 알고 싶다면 [읽어보세요](https://www.infoq.com/presentations/8-lines-code-refactoring).
이러한 아키텍처는 매우 지루하고 이해하기 쉽습니다. 누구나 큰 정신적 노력 없이 파악할 수 있습니다.
아키텍처 검토에 주니어 개발자를 참여시키십시오. 그들은 정신적으로 부담스러운 영역을 식별하는 데 도움을 줄 것입니다.
## 익숙한 프로젝트의 인지 부하 (Cognitive load of familiar projects)
> 문제는 **익숙함이 단순함과 같지 않다**는 것입니다. 익숙함과 단순함은 같은 느낌, 즉 별다른 정신적 노력 없이 공간을 쉽게 이동하는 느낌을 주지만, 매우 다른 이유 때문입니다. 사용하는 모든 "영리한"(즉, "자기 만족적인") 비관용적 트릭은 다른 모든 사람에게 학습 페널티를 부과합니다. 일단 그 학습을 마치면 코드로 작업하는 것이 덜 어렵다는 것을 알게 될 것입니다. 그래서 이미 익숙한 코드를 단순화하는 방법을 인식하기가 어렵습니다. 이것이 제가 "신입"이 너무 제도화되기 전에 코드를 비판하도록 하려는 이유입니다!
>
> 이전 작성자가 이 거대한 혼란을 한 번에 만든 것이 아니라 한 번에 조금씩 점진적으로 만들었을 가능성이 높습니다. 그래서 당신은 이 모든 것을 한 번에 이해하려고 노력한 첫 번째 사람입니다.
>
> 제 수업에서 참여자들은 어느 날 거대한 WHERE 절에 수백 줄의 조건문이 있는 광범위한 SQL 저장 프로시저를 보고 있었습니다. 누군가 어떻게 아무도 이렇게 나빠지도록 내버려 둘 수 있었는지 물었습니다. 저는 이렇게 말했습니다. "조건문이 2~3개밖에 없을 때는 하나를 더 추가해도 아무런 차이가 없습니다. 조건문이 20~30개가 되면 하나를 더 추가해도 아무런 차이가 없습니다!"
>
> 코드베이스에 작용하는 "단순화하는 힘"은 개발자가 내리는 의도적인 선택 외에는 없습니다. 단순화에는 노력이 필요하며, 사람들은 너무 자주 서두릅니다.
>
> *[댄 노스](https://dannorth.net)님의 의견 감사합니다.*
프로젝트의 멘탈 모델을 장기 기억에 내재화했다면 높은 인지 부하를 경험하지 않을 것입니다.
<div align="center">
<img src="/img/mentalmodelsv15.png" alt="멘탈 모델" width="700">
</div>
배워야 할 멘탈 모델이 많을수록 새로운 개발자가 가치를 제공하는 데 더 오랜 시간이 걸립니다.
프로젝트에 새로운 사람을 온보딩할 때 그들이 겪는 혼란의 양을 측정해 보십시오(페어 프로그래밍이 도움이 될 수 있습니다). 만약 그들이 연속으로 40분 이상 혼란스러워한다면 코드에서 개선해야 할 부분이 있는 것입니다.
인지 부하를 낮게 유지하면 사람들이 회사에 합류한 지 몇 시간 만에 코드베이스에 기여할 수 있습니다.
## 결론 (Conclusion)
두 번째 장에서 우리가 추론한 내용이 실제로는 사실이 아니라고 잠시 상상해 보십시오. 만약 그렇다면, 우리가 방금 부정한 결론과 이전 장에서 유효하다고 받아들였던 결론도 정확하지 않을 수 있습니다. `🤯`
느껴지시나요? 의미를 파악하기 위해 기사 전체를 뛰어다녀야 할 뿐만 아니라(얕은 모듈!), 단락 자체가 이해하기 어렵습니다. 우리는 방금 여러분의 머릿속에 불필요한 인지 부하를 만들었습니다. **동료들에게 이런 짓을 하지 마십시오.**
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="똑똑한 저자" width="600">
</div>
우리는 개발 작업에 내재된 것 이상의 모든 인지 부하를 줄여야 합니다.
---
[링크드인](https://www.linkedin.com/in/zakirullin/), [X](https://twitter.com/zakirullin), [깃허브](https://github.com/zakirullin)
[읽기 쉬운 버전](https://minds.md/zakirullin/cognitive)
<details>
<summary><b>댓글</b></summary>
<br>
<p><strong>롭 파이크</strong><br>좋은 기사입니다.</p>
<p><strong><a href="https://x.com/karpathy/status/1872038630405054853" target="_blank">안드레이 카르파티</a></strong> <i>(ChatGPT, 테슬라)</i><br>소프트웨어 공학에 대한 좋은 글입니다. 아마도 가장 사실에 가깝지만 가장 실천되지 않는 관점일 것입니다.</p>
<p><strong><a href="https://x.com/elonmusk/status/1872346903792566655" target="_blank">일론 머스크</a></strong><br>사실입니다.</p>
<p><strong><a href="https://www.linkedin.com/feed/update/urn:li:activity:7277757844970520576/" target="_blank">애디 오스마니</a></strong> <i>(크롬, 세계에서 가장 복잡한 소프트웨어 시스템)</i><br>똑똑한 개발자들이 최신 디자인 패턴과 마이크로서비스를 사용하여 인상적인 아키텍처를 만든 수많은 프로젝트를 보았습니다. 하지만 새로운 팀원이 변경을 시도했을 때, 모든 것이 어떻게 맞춰지는지 이해하는 데만 몇 주를 보냈습니다. 인지 부하가 너무 높아 생산성이 급락하고 버그가 증식했습니다.</p>
<p>아이러니한 점은? 이러한 복잡성을 유발하는 패턴 중 다수가 "클린 코드"라는 이름으로 구현되었다는 것입니다.</p>
<p>정말로 중요한 것은 불필요한 인지 부담을 줄이는 것입니다. 때로는 이것이 많은 얕은 모듈 대신 더 적고 깊은 모듈을 의미하기도 합니다. 때로는 관련된 로직을 작은 함수로 나누는 대신 함께 유지하는 것을 의미하기도 합니다.</p>
<p>그리고 때로는 영리한 해결책보다 지루하고 간단한 해결책을 선택하는 것을 의미하기도 합니다. 최고의 코드는 가장 우아하거나 정교한 코드가 아니라 미래의 개발자(자신 포함)가 빠르게 이해할 수 있는 코드입니다.</p>
<p>당신의 기사는 우리가 브라우저 개발에서 직면하는 문제들과 정말로 공감됩니다. 현대 브라우저가 가장 복잡한 소프트웨어 시스템 중 하나라는 당신의 말은 절대적으로 옳습니다. 크로미움에서 그 복잡성을 관리하는 것은 인지 부하에 대해 당신이 지적한 많은 점들과 완벽하게 일치하는 끊임없는 도전입니다.</p>
<p>크로미움에서 이를 처리하는 한 가지 방법은 신중한 구성 요소 격리와 하위 시스템(렌더링, 네트워킹, 자바스크립트 실행 등) 간의 잘 정의된 인터페이스를 통하는 것입니다. 유닉스 I/O를 사용한 깊은 모듈 예제와 유사하게, 우리는 상대적으로 간단한 인터페이스 뒤에 강력한 기능을 목표로 합니다. 예를 들어, 우리의 렌더링 파이프라인은 엄청난 복잡성(레이아웃, 합성, GPU 가속)을 처리하지만 개발자는 명확한 추상화 계층을 통해 상호 작용할 수 있습니다.</p>
<p>불필요한 추상화를 피하는 것에 대한 당신의 지적도 정말 마음에 와 닿았습니다. 브라우저 개발에서 우리는 웹 표준 및 호환성의 고유한 복잡성을 처리하면서 새로운 기여자가 코드베이스에 접근하기 쉽게 만드는 것 사이에서 끊임없이 균형을 맞춥니다.</p>
<p>때로는 복잡한 시스템에서도 가장 간단한 해결책이 최선일 때가 있습니다.</p>
<p><strong><a href="https://x.com/antirez" target="_blank">antirez</a></strong> <i>(레디스)</i><br>완전히 동의합니다 :) 또한, 언급된 "소프트웨어 설계 철학"에서 빠졌다고 생각하는 것은 "설계 희생"이라는 개념입니다. 즉, 때로는 무언가를 희생하고 단순성이나 성능, 또는 둘 다를 얻을 수 있습니다. 저는 이 아이디어를 지속적으로 적용하지만 종종 이해받지 못합니다.</p>
<p>좋은 예는 제가 항상 해시 항목 만료를 거부했다는 사실입니다. 이것은 설계 희생입니다. 왜냐하면 최상위 항목(키 자체)에만 특정 속성이 있는 경우 설계가 더 간단해지고 값은 그냥 객체가 되기 때문입니다. 레디스에 해시 만료 기능이 추가되었을 때 좋은 기능이었지만 (실제로) 많은 부분에 많은 변경이 필요하여 복잡성이 증가했습니다.</p>
<p>또 다른 예는 제가 지금 하고 있는 벡터 세트, 새로운 레디스 데이터 유형입니다. 저는 레디스가 벡터에 대한 진실의 원천이 아니라 근사치 버전만 가져갈 수 있도록 결정했습니다. 그래서 디스크에 큰 부동 소수점 벡터를 유지하려고 하지 않고 삽입 시 정규화, 양자화를 할 수 있었습니다. 많은 벡터 DB는 사용자가 입력한 내용(전체 정밀도 벡터)을 기억한다는 사실을 희생하지 않습니다.</p>
<p>이것들은 단지 두 가지 임의의 예일 뿐이지만, 저는 이 아이디어를 모든 곳에 적용합니다. 이제 문제는 물론 올바른 것을 희생해야 한다는 것입니다. 종종 매우 큰 복잡성을 차지하는 5%의 기능이 있는데, 그것이 바로 없애야 할 좋은 것입니다 :D</p>
</details>
================================================
FILE: README.md
================================================
# Cognitive load is what matters
[Prompt](https://github.com/zakirullin/cognitive-load/blob/main/README.prompt.md) | [Blog version](https://minds.md/zakirullin/cognitive) | [Chinese](https://github.com/zakirullin/cognitive-load/blob/main/README.zh-cn.md) | [Japanese](README.ja.md) | [Spanish](README.es.md) | [Korean](README.ko.md) | [Turkish](README.tr.md) | [Brazilian Portuguese](README.pt-br) | [Vietnamese](README.vi.md) | [Nepali](README.np.md)
*It is a living document, last update: **October 2025.** Your contributions are welcome!*
## Introduction
There are so many buzzwords and best practices out there, but most of them have failed. They failed because they were imagined, not real. These ideas were based on aesthetics and subjective judgments. We need something more fundamental, something that can't be wrong.
Sometimes we feel confusion going through the code. Confusion costs time and money. Confusion is caused by high *cognitive load*. It's not some fancy abstract concept, but rather **a fundamental human constraint.** It's not imagined, it's there and we can feel it.
Since we spend far more time reading and understanding code than writing it, we should constantly ask ourselves whether we are embedding excessive cognitive load into our code.
## Cognitive load
> Cognitive load is how much a developer needs to think in order to complete a task.
When reading code, you put things like values of variables, control flow logic and call sequences into your head. The average person can hold roughly [four such chunks](https://github.com/zakirullin/cognitive-load/issues/16) in working memory. Once the cognitive load reaches this threshold, it becomes much harder to understand things.
*Let's say we have been asked to make some fixes to a completely unfamiliar project. We were told that a really smart developer had contributed to it. Lots of cool architectures, fancy libraries and trendy technologies were used. In other words, **the author had created a high cognitive load for us.***
<div align="center">
<img src="/img/cognitiveloadv6.png" alt="Cognitive load" width="750">
</div>
We should reduce the cognitive load in our projects as much as possible.
<details>
<summary><b>Cognitive load and interruptions</b></summary>
<div align="center">
<img src="img/interruption.jpeg" width="480">
</div>
</details>
> We are going to use "cognitive load" in an informal sense; sometimes it lines up with the specific scientific concept of Cognitive Load, but we don't know enough about where it does and doesn't match.
## Types of cognitive load
**Intrinsic** - caused by the inherent difficulty of a task. It can't be reduced, it's at the very heart of software development.
**Extraneous** - created by the way the information is presented. Caused by factors not directly relevant to the task, such as smart author's quirks. Can be greatly reduced. We will focus on this type of cognitive load.
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="Intrinsic vs Extraneous" width="600">
</div>
Let's jump straight to the concrete practical examples of extraneous cognitive load.
---
We will refer to the level of cognitive load as follows:
`🧠`: fresh working memory, zero cognitive load
`🧠++`: two facts in our working memory, cognitive load increased
`🤯`: cognitive overload, more than 4 facts
> Our brain is much more complex and unexplored, but we can go with this simplistic model.
## Complex conditionals
```go
if val > someConstant // 🧠+
&& (condition2 || condition3) // 🧠+++, prev cond should be true, one of c2 or c3 has to be true
&& (condition4 && !condition5) { // 🤯, we are messed up by this point
...
}
```
Introduce intermediate variables with meaningful names:
```go
isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
// 🧠, we don't need to remember the conditions, there are descriptive variables
if isValid && isAllowed && isSecure {
...
}
```
## Nested ifs
```go
if isValid { // 🧠+, okay nested code applies to valid input only
if isSecure { // 🧠++, we do stuff for valid and secure input only
stuff // 🧠+++
}
}
```
Compare it with the early returns:
```go
if !isValid
return
if !isSecure
return
// 🧠, we don't really care about earlier returns, if we are here then all good
stuff // 🧠+
```
We can focus on the happy path only, thus freeing our working memory from all sorts of preconditions.
## Inheritance nightmare
We are asked to change a few things for our admin users: `🧠`
`AdminController extends UserController extends GuestController extends BaseController`
Ohh, part of the functionality is in `BaseController`, let's have a look: `🧠+`
Basic role mechanics got introduced in `GuestController`: `🧠++`
Things got partially altered in `UserController`: `🧠+++`
Finally we are here, `AdminController`, let's code stuff! `🧠++++`
Oh, wait, there's `SuperuserController` which extends `AdminController`. By modifying `AdminController` we can break things in the inherited class, so let's dive in `SuperuserController` first: `🤯`
Prefer composition over inheritance. We won't go into detail - there's [plenty of material](https://www.youtube.com/watch?v=hxGOiiR9ZKg) out there.
## Too many small methods, classes or modules
> Method, class and module are interchangeable in this context.
Mantras like "methods should be shorter than 15 lines of code" or "classes should be small" turned out to be somewhat wrong.
**Deep module** - simple interface, complex functionality
**Shallow module** - interface is relatively complex compared to the small functionality it provides
<div align="center">
<img src="/img/deepmodulev8.png" alt="Deep module" width="700">
</div>
Having too many shallow modules can make it difficult to understand the project. **Not only do we have to keep in mind each module's responsibilities, but also all their interactions.** To understand the purpose of a shallow module, we first need to look at the functionality of all the related modules. Jumping between such shallow components is mentally exhausting, <a target="_blank" href="https://blog.separateconcerns.com/2023-09-11-linear-code.html">linear thinking</a> is more natural to us humans.
> Information hiding is paramount, and we don't hide as much complexity in shallow modules.
I have two pet projects, both of them are somewhat 5K lines of code. The first one has 80 shallow classes, whereas the second one has only 7 deep classes. I haven't been maintaining any of these projects for one year and a half.
Once I came back, I realised that it was extremely difficult to untangle all the interactions between those 80 classes in the first project. I would have to rebuild an enormous amount of cognitive load before I could start coding. On the other hand, I was able to grasp the second project quickly, because it had only a few deep classes with a simple interface.
> The best components are those that provide powerful functionality yet have a simple interface.
>
> *John Ousterhout, A Philosophy of Software Design*
The interface of the Unix I/O is very simple. It has only five basic calls:
```python
open(path, flags, permissions)
read(fd, buffer, count)
write(fd, buffer, count)
lseek(fd, offset, referencePosition)
close(fd)
```
A modern implementation of this interface has **hundreds of thousands of lines of code.** Lots of complexity is hidden under the hood. Yet it is easy to use due to its simple interface.
> This deep module example is taken from the book [A Philosophy of Software Design](https://web.stanford.edu/~ouster/cgi-bin/book.php) by John Ousterhout. Not only does this book cover the very essence of complexity in software development, but it also has the greatest interpretation of Parnas' influential paper [On the Criteria To Be Used in Decomposing Systems into Modules](https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf). Both are essential reads. Other related readings: [A Philosophy of Software Design vs Clean Code](https://github.com/johnousterhout/aposd-vs-clean-code), [It's probably time to stop recommending Clean Code](https://qntm.org/clean), [Small Functions considered Harmful](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29).
<details>
<summary><b>Important things should be big, examples</b></summary>
<br>
<div align="center">
<img src="/img/dirty.png" alt="Clean vs Dirty" width="600">
</div>
<blockquote>If you allow your important "crux" functions to be larger ("dirty") it is easier to pick them out from the sea of functions, they are obviously important: just look at them, they are big!</blockquote>
This picture is taken from <a href="https://htmx.org/essays/codin-dirty/" target="_blank">Codin' Dirty</a> article by Carson Gross. You'll find <a href="https://htmx.org/essays/codin-dirty/#real-world-examples" target="_blank">real world examples</a> of deep functions there.
</details>
P.S. If you think we are rooting for bloated God objects with too many responsibilities, you got it wrong.
## Responsible for one thing
All too often, we end up creating lots of shallow modules, following some vague "a module should be responsible for one, and only one, thing" principle. What is this blurry one thing? Instantiating an object is one thing, right? So [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks) seems to be just fine. **The names and interfaces of such classes tend to be more mentally taxing than their entire implementations, what kind of abstraction is that?** Something went wrong.
We make changes to our systems to satisfy our users and stakeholders. We are responsible to them.
> A module should be responsible to one, and only one, user or stakeholder.
This is what this Single Responsibility Principle is all about. Simply put, if we introduce a bug in one place, and then two different business people come to complain, we've violated the principle. It has nothing to do with the number of things we do in our module.
But even now, this rule can do more harm than good. This principle can be understood in as many different ways as there are individuals. A better approach would be to look at how much cognitive load it all creates. It's mentally demanding to remember that change in one place can trigger a chain of reactions across different business streams. And that's about it, no fancy terms to learn.
## Too many shallow microservices
This shallow-deep module principle is scale-agnostic, and we can apply it to microservices architecture. Too many shallow microservices won't do any good - the industry is heading towards somewhat "macroservices", i.e., services that are not so shallow (=deep). One of the worst and hardest to fix phenomena is so-called distributed monolith, which is often the result of this overly granular shallow separation.
I once consulted a startup where a team of five developers introduced 17(!) microservices. They were 10 months behind schedule and appeared nowhere close to the public release. Every new requirement led to changes in 4+ microservices. It took an enormous amount of time to reproduce and debug an issue in such a distributed system. Both time to market and cognitive load were unacceptably high. `🤯`
Is this the right way to approach the uncertainty of a new system? It's enormously difficult to elicit the right logical boundaries in the beginning. The key is to make decisions as late as you can responsibly wait, because that is when you have the most information at hand. By introducing a network layer up front, we make our design decisions hard to revert right from the start. The team's only justification was: "The FAANG companies proved microservices architecture to be effective". *Hello, you got to stop dreaming big.*
The [Tanenbaum-Torvalds debate](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate) argued that Linux's monolithic design was flawed and obsolete, and that a microkernel architecture should be used instead. Indeed, the microkernel design seemed to be superior "from a theoretical and aesthetical" point of view. On the practical side of things - three decades on, microkernel-based GNU Hurd is still in development, and monolithic Linux is everywhere. This page is powered by Linux, your smart teapot is powered by Linux. By monolithic Linux.
A well-crafted monolith with truly isolated modules is often much more flexible than a bunch of microservices. It also requires far less cognitive effort to maintain. It's only when the need for separate deployments becomes crucial, such as scaling the development team, that you should consider adding a network layer between the modules, future microservices.
## Feature-rich languages
We feel excited when new features got released in our favourite language. We spend some time learning these features, we build code upon them.
If there are lots of features, we may spend half an hour playing with a few lines of code, to use one or another feature. And it's kind of a waste of time. But what's worse, **when you come back later, you would have to recreate that thought process!**
**You not only have to understand this complicated program, you have to understand why a programmer decided this was the way to approach a problem from the features that are available.** `🤯`
These statements are made by none other than Rob Pike.
> Reduce cognitive load by limiting the number of choices.
Language features are OK, as long as they are orthogonal to each other.
<details>
<summary><b>Thoughts from an engineer with 20 years of C++ experience ⭐️</b></summary>
<br>
I was looking at my RSS reader the other day and noticed that I have somewhat three hundred unread articles under the "C++" tag. I haven't read a single article about the language since last summer, and I feel great!<br><br>
I've been using C++ for 20 years for now, that's almost two-thirds of my life. Most of my experience lies in dealing with the darkest corners of the language (such as undefined behaviours of all sorts). It's not a reusable experience, and it's kind of creepy to throw it all away now.<br><br>
Like, can you imagine, the token <code>||</code> has a different meaning in <code>requires ((!P<T> || !Q<T>))</code> and in <code>requires (!(P<T> || Q<T>))</code>. The first is the constraint disjunction, the second is the good-old logical OR operator, and they behave differently.<br><br>
You can't allocate space for a trivial type and just <code>memcpy</code> a set of bytes there without extra effort - that won't start the lifetime of an object. This was the case before C++20. It was fixed in C++20, but the cognitive load of the language has only increased.<br><br>
Cognitive load is constantly growing, even though things got fixed. I should know what was fixed, when it was fixed, and what it was like before. I am a professional after all. Sure, C++ is good at legacy support, which also means that you <b>will face</b> that legacy. For example, last month a colleague of mine asked me about some behaviour in C++03. <code>🤯</code><br><br>
There were 20 ways of initialization. Uniform initialization syntax has been added. Now we have 21 ways of initialization. By the way, does anyone remember the rules for selecting constructors from the initializer list? Something about implicit conversion with the least loss of information, <i>but if</i> the value is known statically, then... <code>🤯</code><br><br>
<b>This increased cognitive load is not caused by a business task at hand. It is not an intrinsic complexity of the domain. It is just there due to historical reasons</b> (<i>extraneous cognitive load</i>).<br><br>
I had to come up with some rules. Like, if that line of code is not as obvious and I have to remember the standard, I better not write it that way. The standard is somewhat 1500 pages long, by the way.<br><br>
<b>By no means I am trying to blame C++.</b> I love the language. It's just that I am tired now.<br><br>
<p>Thanks to <a href="https://0xd34df00d.me" target="_blank">0xd34df00d</a> for writing.</p>
</details>
## Business logic and HTTP status codes
On the backend we return:
`401` for expired JWT token
`403` for not enough access
`418` for banned users
The engineers on the frontend use backend API to implement login functionality. They would have to temporarily create the following cognitive load in their brains:
`401` is for expired JWT token // `🧠+`, ok just temporarily remember it
`403` is for not enough access // `🧠++`
`418` is for banned users // `🧠+++`
Frontend developers would (hopefully) introduce some kind `numeric status -> meaning` dictionary on their side, so that subsequent generations of contributors wouldn't have to recreate this mapping in their brains.
Then QA engineers come into play:
"Hey, I got `403` status, is that expired token or not enough access?"
**QA engineers can't jump straight to testing, because first they have to recreate the cognitive load that the engineers on the backend once created.**
Why hold this custom mapping in our working memory? It's better to abstract away your business details from the HTTP transfer protocol, and return self-descriptive codes directly in the response body:
```json
{
"code": "jwt_has_expired"
}
```
Cognitive load on the frontend side: `🧠` (fresh, no facts are held in mind)
Cognitive load on the QA side: `🧠`
The same rule applies to all sorts of numeric statuses (in the database or wherever) - **prefer self-describing strings.** We are not in the era of 640K computers to optimise for memory.
> People spend time arguing between `401` and `403`, making decisions based on their own mental models. New developers are coming in, and they need to recreate that thought process. You may have documented the "whys" (ADRs) for your code, helping newcomers to understand the decisions made. But in the end it just doesn't make any sense. We can separate errors into either user-related or server-related, but apart from that, things are kind of blurry.
P.S. It's often mentally taxing to distinguish between "authentication" and "authorization". We can use simpler terms like ["login" and "permissions"](https://ntietz.com/blog/lets-say-instead-of-auth/) to reduce the cognitive load.
## Abusing DRY principle
Do not repeat yourself - that is one of the first principles you are taught as a software engineer. It is so deeply embedded in ourselves that we can not stand the fact of a few extra lines of code. Although in general a good and fundamental rule, when overused it leads to the cognitive load we can not handle.
Nowadays, everyone builds software based on logically separated components. Often those are distributed among multiple codebases representing separate services. When you strive to eliminate any repetition, you might end up creating tight coupling between unrelated components. As a result, changes in one part may have unintended consequences in other seemingly unrelated areas. It can also hinder the ability to replace or modify individual components without impacting the entire system. `🤯`
In fact, the same problem arises even within a single module. You might extract common functionality too early, based on perceived similarities that might not actually exist in the long run. This can result in unnecessary abstractions that are difficult to modify or extend.
Rob Pike once said:
> A little copying is better than a little dependency.
We are tempted to not reinvent the wheel so strongly that we are ready to import large, heavy libraries to use a small function that we could easily write by ourselves.
**All your dependencies are your code.** Going through 10+ levels of stack trace of some imported library and figuring out what went wrong (*because things go wrong*) is painful.
## Tight coupling with a framework
There's a lot of "magic" in frameworks. By relying too heavily on a framework, **we force all upcoming developers to learn that "magic" first.** It can take months. Even though frameworks enable us to launch MVPs in a matter of days, in the long run they tend to add unnecessary complexity and cognitive load.
Worse yet, at some point frameworks can become a significant constraint when faced with a new requirement that just doesn't fit the architecture. From here onwards people end up forking a framework and maintaining their own custom version. Imagine the amount of cognitive load a newcomer would have to build (i.e. learn this custom framework) in order to deliver any value. `🤯`
**By no means do we advocate to invent everything from scratch!**
We can write code in a somewhat framework-agnostic way. The business logic should not reside within a framework; rather, it should use the framework's components. Put a framework outside of your core logic. Use the framework in a library-like fashion. This would allow new contributors to add value from day one, without the need of going through debris of framework-related complexity first.
> [Why I Hate Frameworks](https://minds.md/benji/frameworks)
## Layered architecture
There is a certain engineering excitement about all this stuff.
I myself was a passionate advocate of Hexagonal/Onion Architecture for years. I used it here and there and encouraged other teams to do so. The complexity of our projects went up, the sheer number of files alone had doubled. It felt like we were writing a lot of glue code. On ever changing requirements we had to make changes across multiple layers of abstractions, it all became tedious. `🤯`
**Abstraction is supposed to hide complexity, here it just adds [indirection](https://fhur.me/posts/2024/thats-not-an-abstraction).** Jumping from call to call to read along and figure out what goes wrong and what is missing is a vital requirement to quickly solve a problem. With this architecture’s layer uncoupling it requires an exponential factor of extra, often disjointed, traces to get to the point where the failure occurs. Every such trace takes space in our limited working memory. `🤯`
<div align="center">
<img src="/img/layers.png" alt="Layers" width="400">
</div>
This architecture was something that made intuitive sense at first, but every time we tried applying it to projects it did more harm than good. We spent years on unnecessary mental activity and writing useless glue code with no clear business value. On the contrary, we made things worse for the business by forcing newcomers to learn our approaches (mental models) first. The time to market has worsened. In the end, we gave it all up in favour of the good old dependency inversion principle. **No port/adapter terms to learn, no unnecessary layers of horizontal abstractions, no extraneous cognitive load.**
If you think that such layering will allow you to quickly replace a database or other dependencies, you're mistaken. Changing the storage causes lots of problems, and believe us, having some abstractions for the data access layer is the least of your worries. At best, abstractions can save somewhat 10% of your migration time (if any), the real pain is in data model incompatibilities, communication protocols, distributed systems challenges, and [implicit interfaces](https://www.hyrumslaw.com).
> With a sufficient number of users of an API,
> it does not matter what you promise in the contract:
> all observable behaviours of your system
> will be depended on by somebody.
We did a storage migration, and that took us about 10 months. The old system was single-threaded, so the exposed events were sequential. All our systems depended on that observed behaviour. This behaviour was not part of the API contract, it was not reflected in the code. A new distributed storage didn't have that guarantee - the events came out-of-order. We spent only a few hours coding a new storage adapter, thanks to an abstraction. **We spent the next 10 months on dealing with out-of-order events and other challenges.** It's now funny to say that abstractions help us replace components quickly.
**So, why pay the price of high cognitive load for such a layered architecture, if it doesn't pay off in the future?** Plus, in most cases, that future of replacing some core component never happens.
These architectures are not fundamental, they are just subjective, biased consequences of more fundamental principles. Why rely on those subjective interpretations? Follow the fundamental rules instead: dependency inversion principle, single source of truth, cognitive load and information hiding. Your business logic should not depend on low-level modules like database, UI or framework. We should be able to write tests for our core logic without worrying about the infrastructure, and that's it. [Discuss](https://github.com/zakirullin/cognitive-load/discussions/24).
Do not add layers of abstractions for the sake of an architecture. Add them whenever you need an extension point that is justified for practical reasons.
**[Layers of abstraction aren't free of charge](https://blog.jooq.org/why-you-should-not-implement-layered-architecture), they are to be held in our limited working memory.**
## Domain-driven design
Domain-driven design has some great points, although it is often misinterpreted. People say, "We write code in DDD", which is a bit strange, because DDD is more about the problem space rather than the solution space.
Ubiquitous language, domain, bounded context, aggregate, event storming are all about problem space. They are meant to help us learn the insights about the domain and extract the boundaries. DDD enables developers, domain experts and business people to communicate effectively using a single, unified language. Rather than focusing on these problem space aspects of DDD, we tend to emphasise particular folder structures, services, repositories, and other solution space techniques.
Chances are that the way we interpret DDD is likely to be unique and subjective. And if we build code upon this understanding, i.e., if we create a lot of extraneous cognitive load - future developers are doomed. `🤯`
Team Topologies provides a much better, easier to understand framework that helps us split the cognitive load across teams. Engineers tend to develop somewhat similar mental models after learning about Team Topologies. DDD, on the other hand, seems to be creating 10 different mental models for 10 different readers. Instead of being common ground, it becomes a battleground for unnecessary debates.
## Cognitive load in familiar projects
> The problem is that **familiarity is not the same as simplicity.** They *feel* the same — that same ease of moving through a space without much mental effort — but for very different reasons. Every “clever” (read: “self-indulgent”) and non-idiomatic trick you use incurs a learning penalty for everyone else. Once they have done that learning, then they will find working with the code less difficult. So it is hard to recognise how to simplify code that you are already familiar with. This is why I try to get “the new kid” to critique the code before they get too institutionalised!
>
> It is likely that the previous author(s) created this huge mess one tiny increment at a time, not all at once. So you are the first person who has ever had to try to make sense of it all at once.
>
> In my class I describe a sprawling SQL stored procedure we were looking at one day, with hundreds of lines of conditionals in a huge WHERE clause. Someone asked how anyone could have let it get this bad. I told them: “When there are only 2 or 3 conditionals, adding another one doesn’t make any difference. By the time there are 20 or 30 conditionals, adding another one doesn’t make any difference!”
>
> There is no “simplifying force” acting on the code base other than deliberate choices that you make. Simplifying takes effort, and people are too often in a hurry.
>
> *Thanks to [Dan North](https://dannorth.net) for his comment*.
If you've internalized the mental models of the project into your long-term memory, you won't experience a high cognitive load.
<div align="center">
<img src="/img/mentalmodelsv15.png" alt="Mental models" width="700">
</div>
The more unique mental models there are to learn, the longer it takes for a new developer to deliver value. If you keep the cognitive load low, people can contribute to your codebase within the first few hours of joining your company. And that doesn't mean we sacrifice in quality, or allow piles of mud to emerge.
> What are those unique mental models? It's some set of rules, usually a mixture of Clean Architecture/Event Driven Architecture/DDD. This is an author's own interpretation of the things that excite him the most. His own subjective mental models. **Extraneous cognitive load that others have to internalize.**
Once you onboard new people on your project, try to measure the amount of confusion they have (pair programming may help). If they're confused for more than ~40 minutes in a row - you've got things to improve in your code.
## Examples
> Software systems are perhaps the most intricate and complex (in terms of number of distinct kinds of parts) of the things humanity makes.
>
> *Fred Brooks, The Mythical Man-Month*
- Our architecture is a standard CRUD app architecture, [a Python monolith on top of Postgres](https://danluu.com/simple-architectures/)
- How Instagram scaled to 14 million users with [only 3 engineers](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)
- The companies where we were like ”woah, these folks are [smart as hell](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)” for the most part failed
- One function that wires up the entire system. If you want to know how the system works - [go read it](https://www.infoq.com/presentations/8-lines-code-refactoring)
- Designing for Understandability: [The Raft Consensus Algorithm](https://www.youtube.com/watch?v=vYp4LYbnnW8)
These architectures are quite boring and easy to understand. Anyone can grasp them without much mental effort.
<details>
<summary><b>Coding principles and experience</b></summary>
<div align="center">
<img src="img/complexity.png" alt="Super simple code" width="500">
</div>
<a href="https://twitter.com/flaviocopes">@flaviocopes</a>
</details>
Involve junior developers in architecture reviews, they will help you to identify the mentally demanding areas.
**Maintaining software is hard**, things break and we would need every bit of mental effort we can save. The fewer components there are in the system, the fewer issues there will be. Debugging will also be less mentally taxing.
> Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.
>
> *Brian Kernighan*
In general, the mindset "Wow, this architecture sure feels good!" is misleading. That's "a point in time" subjective feeling, and it says nothing about the reality. A far better approach is to observe the consequences in the long run:
- Is it easy to reproduce and debug an issue? Or do you have to jump across the call stacks or distributed components, trying to make sense of everything in your head?
- Can we make changes quickly, or are there a lot of unknown unknowns, and people are afraid to touch things?
- Can new people add features quickly? Are there some unique mental models to learn?
These questions are far harder to track, and people often don't like to answer them directly. Look at some of the most complex software systems in the world, the ones that have stood the test of time - Unix, Kubernetes, Chrome and Redis (see comments below). You will not find anything fancy there, it's boring for the most part, and that's a good thing.
## Conclusion
Imagine for a moment that what we inferred in the second chapter isn’t actually true. If that’s the case, then the conclusion we just negated, along with the conclusions in the previous chapter that we had accepted as valid, might not be correct either. `🤯`
Do you feel it? Not only do you have to jump all over the article to get the meaning (shallow modules!), but the paragraph in general is difficult to understand. We have just created an unnecessary cognitive load in your head. **Do not do this to your colleagues.**
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="Smart author" width="600">
</div>
We should reduce any cognitive load above and beyond what is intrinsic to the work we do.
---
[LinkedIn](https://www.linkedin.com/in/zakirullin/), [X](https://twitter.com/zakirullin), [GitHub](https://github.com/zakirullin), artemzr(аt)g-yоu-knоw-com
<details>
<summary><b>Comments (6)</b></summary>
<br>
<p><strong>Rob Pike</strong> <i>(Unix, Golang)</i><br>Nice article.</p>
<p><strong><a href="https://x.com/karpathy/status/1872038630405054853" target="_blank">Andrej Karpathy</a></strong> <i>(ChatGPT, Tesla)</i><br>Nice post on software engineering. Probably the most true, least practiced viewpoint.</p>
<p><strong><a href="https://x.com/elonmusk/status/1872346903792566655" target="_blank">Elon Musk</a></strong> <i>(Rockets)</i><br>True.</p>
<p><strong><a href="https://www.linkedin.com/feed/update/urn:li:activity:7277757844970520576/" target="_blank">Addy Osmani</a></strong> <i>(Chrome, the most complex software system in the world)</i><br>I've seen countless projects where smart developers created impressive architectures using the latest design patterns and microservices. But when new team members tried to make changes, they spent weeks just trying to understand how everything fits together. The cognitive load was so high that productivity plummeted and bugs multiplied.</p>
<p>The irony? Many of these complexity-inducing patterns were implemented in the name of "clean code."</p>
<p>What really matters is reducing unnecessary cognitive burden. Sometimes this means fewer, deeper modules instead of many shallow ones. Sometimes it means keeping related logic together instead of splitting it into tiny functions.</p>
<p>And sometimes it means choosing boring, straightforward solutions over clever ones. The best code isn't the most elegant or sophisticated - it's the code that future developers (including yourself) can understand quickly.</p>
<p>Your article really resonates with the challenges we face in browser development. You're absolutely right about modern browsers being among the most complex software systems. Managing that complexity in Chromium is a constant challenge that aligns perfectly with many of the points you made about cognitive load.</p>
<p>One way we try to handle this in Chromium is through careful component isolation and well-defined interfaces between subsystems (like rendering, networking, JavaScript execution, etc.). Similar to your deep modules example with Unix I/O - we aim for powerful functionality behind relatively simple interfaces. For instance, our rendering pipeline handles incredible complexity (layout, compositing, GPU acceleration) but developers can interact with it through clear abstraction layers.</p>
<p>Your points about avoiding unnecessary abstractions really hit home too. In browser development, we constantly balance between making the codebase approachable for new contributors while handling the inherent complexity of web standards and compatibility. </p>
<p>Sometimes the simplest solution is the best one, even in a complex system.</p>
<p><strong><a href="https://x.com/antirez" target="_blank">antirez</a></strong> <i>(Redis)</i><br>Totally agree about it :) Also, what I believe is missing from mentioned "A Philosophy of Software Design" is the concept of "design sacrifice". That is, sometimes you sacrifice something and get back simplicity, or performances, or both. I apply this idea continuously, but often is not understood.</p>
<p>A good example is the fact that I always refused to have hash items expires. This is a design sacrifice because if you have certain attributes only in the top-level items (the keys themselves), the design is simpler, values will just be objects. When Redis got hash expires, it was a nice feature but required (indeed) many changes to many parts, raising the complexity.</p>
<p>Another example is what I'm doing right now, Vector Sets, the new Redis data type. I decided that Redis would not be the source of truth about vectors, but that it can just take an approximate version of them, so I was able to do on-insert normalization, quantization without trying to retain the large floats vector on disk, and so forth. Many vector DBs don't sacrifice the fact of remembering what the user put inside (the full precision vector).</p>
<p>These are just two random examples, but I apply this idea everywhere. Now the thing is: of course one must sacrifice the right things. Often, there are 5% features that account for a very large amount of complexity: that is a good thing to kill :D</p>
<p><strong><a href="https://working-for-the-future.medium.com/about" target="_blank">A developer from the internet</a></strong><br>You would not hire me... I sell myself on my track record of released enterprise projects.</p>
<p>I worked with a guy that could speak design patterns. I could never speak that way, though I was one of the few that could well understand him. The managers loved him and he could dominate any development conversation. The people working around him said he left a trail of destruction behind him. I was told that I was the first person that could understand his projects. Maintainability matters. I care most about TCO (<i>Total Cost of Ownership</i>). For some firms, that's what matters.</p>
<p>I logged into Github after not being there for a while and for some reason it took me to an article in a repository by someone that seemed random. I was thinking "what is this" and had some trouble getting to my home page, so I read it. I didn't really register it at the time, but it was amazing. Every developer should read it. It largely said that almost everything we've been told about programming best practices leads to excessive "cognitive load", meaning our minds are getting kicked by the intellectual demands. I've known this for a while, especially with the demands of cloud, security and DevOps.</p>
<p>I also liked it because it described practices I have done for decades, but never much admit to because they are not popular... I write really complicated stuff and need all the help I can get.</p>
<p>Consider, if I'm right, it popped up because the Github folks, very smart people, though that developers should see it. I agree.</p>
<p><a href="https://news.ycombinator.com/item?id=45074248" target="_blank">Comments on Hacker News</a> (<a href="https://news.ycombinator.com/item?id=42489645" target="_blank">2</a>)</p>
</details>
================================================
FILE: README.np.md
================================================
# संज्ञानात्मक भार नै महत्त्वपूर्ण छ
[प्रोम्प्ट](https://github.com/zakirullin/cognitive-load/blob/main/README.prompt.md) | [ब्लग संस्करण](https://minds.md/zakirullin/cognitive) | [चिनियाँ](https://github.com/zakirullin/cognitive-load/blob/main/README.zh-cn.md) | [कोरियाली](README.ko.md) | [टर्किश](README.tr.md) | [जापानी](README.ja.md) | [भियतनामी](README.vi.md)
*यो एक जीवित दस्तावेज हो, अन्तिम अपडेट: **अक्टोबर २०२५।** तपाईंको योगदान स्वागत छ!*
## परिचय
त्यहाँ धेरै buzzwords र उत्तम अभ्यासहरू छन्, तर तिनीहरूमध्ये धेरै असफल भएका छन्। तिनीहरू असफल भए किनभने तिनीहरू कल्पना गरिएका थिए, वास्तविक होइनन्। यी विचारहरू सौन्दर्य र व्यक्तिपरक निर्णयहरूमा आधारित थिए। हामीलाई केही अधिक आधारभूत चाहिन्छ, केही जुन गलत हुन सक्दैन।
कहिलेकाहीं हामी कोड मार्फत जाँदा भ्रम महसुस गर्छौं। भ्रमले समय र पैसा खर्च गर्छ। भ्रम उच्च *संज्ञानात्मक भार* को कारणले गर्दा हुन्छ। यो केही फ्यान्सी अमूर्त अवधारणा होइन, बरु **एक आधारभूत मानव बाधा हो।** यो कल्पना गरिएको होइन, यो त्यहाँ छ र हामी यसलाई महसुस गर्न सक्छौं।
हामी कोड लेख्नु भन्दा धेरै समय पढ्न र बुझ्न खर्च गर्छौं, त्यसैले हामीले निरन्तर आफैलाई सोध्नुपर्छ कि हामी हाम्रो कोडमा अत्यधिक संज्ञानात्मक भार इम्बेड गर्दैछौं कि छैनौं।
## संज्ञानात्मक भार
> संज्ञानात्मक भार भनेको कुनै कार्य पूरा गर्न विकासकर्ताले कति सोच्नु पर्छ।
कोड पढ्दा, तपाईंले चरहरूको मानहरू, नियन्त्रण प्रवाह तर्क र कल अनुक्रमहरू जस्ता चीजहरू आफ्नो टाउकोमा राख्नुहुन्छ। औसत व्यक्तिले कार्य सम्झनामा लगभग [चार यस्ता खण्डहरू](https://github.com/zakirullin/cognitive-load/issues/16) राख्न सक्छ। एक पटक संज्ञानात्मक भार यो सीमामा पुग्छ, चीजहरू बुझ्न धेरै गाह्रो हुन्छ।
*मानौं हामीलाई पूर्ण रूपमा अपरिचित परियोजनामा केही समाधानहरू गर्न भनिएको छ। हामीलाई भनिएको थियो कि वास्तवमा स्मार्ट विकासकर्ताले यसमा योगदान गरेको थियो। धेरै राम्रो architectures, फ्यान्सी पुस्तकालयहरू र ट्रेंडी प्रविधिहरू प्रयोग गरियो। अर्को शब्दमा, **लेखकले हाम्रो लागि उच्च संज्ञानात्मक भार सिर्जना गरेको थियो।***
<div align="center">
<img src="/img/cognitiveloadv6.png" alt="Cognitive load" width="750">
</div>
हामीले हाम्रो परियोजनाहरूमा संज्ञानात्मक भार सकेसम्म कम गर्नुपर्छ।
<details>
<summary><b>संज्ञानात्मक भार र अवरोधहरू</b></summary>
<div align="center">
<img src="img/interruption.jpeg" width="480">
</div>
</details>
> हामी "संज्ञानात्मक भार" लाई अनौपचारिक अर्थमा प्रयोग गर्न जाँदैछौं; कहिलेकाहीं यो संज्ञानात्मक भार को विशिष्ट वैज्ञानिक अवधारणा संग मेल खान्छ, तर हामी यो कहाँ मेल खान्छ र कहाँ खाँदैन भन्ने बारेमा पर्याप्त जान्दैनौं।
## संज्ञानात्मक भारका प्रकारहरू
**आन्तरिक** - कार्यको निहित कठिनाइले गर्दा हुन्छ। यसलाई कम गर्न सकिँदैन, यो सफ्टवेयर विकासको मुटुमा छ।
**बाह्य** - जानकारी प्रस्तुत गरिएको तरिकाले सिर्जना गरिएको। कार्यसँग सीधा सम्बन्धित नभएका कारकहरूले गर्दा हुन्छ, जस्तै स्मार्ट लेखकको विचित्रता। धेरै कम गर्न सकिन्छ। हामी यस प्रकारको संज्ञानात्मक भारमा ध्यान केन्द्रित गर्नेछौं।
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="Intrinsic vs Extraneous" width="600">
</div>
आउनुहोस् सीधा बाह्य संज्ञानात्मक भारको ठोस व्यावहारिक उदाहरणहरूमा जाऔं।
---
हामी संज्ञानात्मक भारको स्तरलाई निम्नानुसार सन्दर्भ गर्नेछौं:
`🧠`: ताजा कार्य सम्झना, शून्य संज्ञानात्मक भार
`🧠++`: हाम्रो कार्य सम्झनामा दुई तथ्यहरू, संज्ञानात्मक भार बढ्यो
`🤯`: संज्ञानात्मक ओभरलोड, ४ भन्दा बढी तथ्यहरू
> हाम्रो मस्तिष्क धेरै जटिल र अन्वेषण नगरिएको छ, तर हामी यो सरल मोडेलसँग जान सक्छौं।
## जटिल सर्तहरू
```go
if val > someConstant // 🧠+
&& (condition2 || condition3) // 🧠+++, अघिल्लो सर्त सत्य हुनुपर्छ, c2 वा c3 मध्ये एक सत्य हुनुपर्छ
&& (condition4 && !condition5) { // 🤯, यो बिन्दुमा हामी गडबड छौं
...
}
```
अर्थपूर्ण नामहरू भएका मध्यवर्ती चरहरू परिचय गर्नुहोस्:
```go
isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
// 🧠, हामीले सर्तहरू सम्झनु पर्दैन, वर्णनात्मक चरहरू छन्
if isValid && isAllowed && isSecure {
...
}
```
## नेस्टेड ifs
```go
if isValid { // 🧠+, ठीक छ नेस्टेड कोड मान्य इनपुटमा मात्र लागू हुन्छ
if isSecure { // 🧠++, हामी मान्य र सुरक्षित इनपुटको लागि मात्र काम गर्छौं
stuff // 🧠+++
}
}
```
यसलाई प्रारम्भिक रिटर्न्ससँग तुलना गर्नुहोस्:
```go
if !isValid
return
if !isSecure
return
// 🧠, हामी पहिलेको रिटर्न्सको बारेमा वास्तवमा वास्ता गर्दैनौं, यदि हामी यहाँ छौं भने सबै राम्रो छ
stuff // 🧠+
```
हामी केवल खुसी मार्गमा ध्यान केन्द्रित गर्न सक्छौं, यसरी हाम्रो कार्य सम्झनालाई सबै प्रकारका पूर्व-शर्तहरूबाट मुक्त गर्दै।
## इन्हेरिटेन्स दुःस्वप्न
हामीलाई हाम्रो प्रशासक प्रयोगकर्ताहरूको लागि केही चीजहरू परिवर्तन गर्न भनिएको छ: `🧠`
`AdminController extends UserController extends GuestController extends BaseController`
ओह, कार्यक्षमताको केही भाग `BaseController` मा छ, हेरौं: `🧠+`
आधारभूत भूमिका मेकानिक्स `GuestController` मा प्रस्तुत गरियो: `🧠++`
चीजहरू आंशिक रूपमा `UserController` मा परिवर्तन गरियो: `🧠+++`
अन्तमा हामी यहाँ छौं, `AdminController`, कोड गरौं! `🧠++++`
ओह, पर्खनुहोस्, त्यहाँ `SuperuserController` छ जसले `AdminController` लाई विस्तार गर्छ। `AdminController` परिमार्जन गरेर हामी इन्हेरिट गरिएको क्लासमा चीजहरू तोड्न सक्छौं, त्यसैले पहिले `SuperuserController` मा गोता लगाऔं: `🤯`
इन्हेरिटेन्स भन्दा कम्पोजिसनलाई प्राथमिकता दिनुहोस्। हामी विस्तृत विवरणमा जाँदैनौं - त्यहाँ [धेरै सामग्री](https://www.youtube.com/watch?v=hxGOiiR9ZKg) छ।
## धेरै साना मेथडहरू, क्लासहरू वा मोड्युलहरू
> मेथड, क्लास र मोड्युल यो सन्दर्भमा आदान-प्रदान गर्न मिल्छन्।
"मेथडहरू १५ लाइन कोड भन्दा छोटो हुनुपर्छ" वा "क्लासहरू सानो हुनुपर्छ" जस्ता मन्त्रहरू केही हदसम्म गलत साबित भए।
**गहिरो मोड्युल** - सरल इन्टरफेस, जटिल कार्यक्षमता
**उथली मोड्युल** - इन्टरफेस यसले प्रदान गर्ने सानो कार्यक्षमताको तुलनामा अपेक्षाकृत जटिल छ
<div align="center">
<img src="/img/deepmodulev8.png" alt="Deep module" width="700">
</div>
धेरै उथला मोड्युलहरू हुनुले परियोजना बुझ्न गाह्रो बनाउन सक्छ। **हामीले प्रत्येक मोड्युलको जिम्मेवारीहरू मात्र होइन, तर तिनीहरूको सबै अन्तरक्रियाहरू पनि दिमागमा राख्नुपर्छ।** उथले मोड्युलको उद्देश्य बुझ्न, हामीले पहिले सबै सम्बन्धित मोड्युलहरूको कार्यक्षमता हेर्नुपर्छ। यस्ता उथले घटकहरू बीच उफ्रनु मानसिक रूपमा थकाइ लाग्दो छ, <a target="_blank" href="https://blog.separateconcerns.com/2023-09-11-linear-code.html">रैखिक सोच</a> हामी मानिसहरूको लागि अधिक प्राकृतिक छ।
> जानकारी लुकाउनु सर्वोपरि छ, र हामी उथले मोड्युलहरूमा धेरै जटिलता लुकाउँदैनौं।
मसँग दुई पेट परियोजनाहरू छन्, दुवै लगभग ५K लाइन कोड छन्। पहिलो एकमा ८० उथला क्लासहरू छन्, जबकि दोस्रोमा केवल ७ गहिरो क्लासहरू छन्। मैले यी मध्ये कुनै पनि परियोजना डेढ वर्षसम्म कायम राखेको छैन।
एक पटक म फर्किएँ, मैले महसूस गरें कि पहिलो परियोजनामा ती ८० क्लासहरू बीचको सबै अन्तरक्रियाहरू खोल्न अत्यन्तै गाह्रो थियो। म कोडिङ सुरु गर्नु अघि मैले ठूलो मात्रामा संज्ञानात्मक भार पुनर्निर्माण गर्नुपर्ने थियो। अर्कोतर्फ, म दोस्रो परियोजना छिटो बुझ्न सकें, किनभने यसमा केवल केही गहिरो क्लासहरू सरल इन्टरफेससहित थिए।
> उत्तम घटकहरू ती हुन् जसले शक्तिशाली कार्यक्षमता प्रदान गर्छन् तर सरल इन्टरफेस छ।
>
> *John Ousterhout, A Philosophy of Software Design*
Unix I/O को इन्टरफेस धेरै सरल छ। यसमा केवल पाँच आधारभूत कलहरू छन्:
```python
open(path, flags, permissions)
read(fd, buffer, count)
write(fd, buffer, count)
lseek(fd, offset, referencePosition)
close(fd)
```
यस इन्टरफेसको आधुनिक कार्यान्वयनमा **लाखौं लाइन कोड छ।** धेरै जटिलता हुड मुनि लुकेको छ। तर यसको सरल इन्टरफेसको कारण प्रयोग गर्न सजिलो छ।
> यो गहिरो मोड्युल उदाहरण John Ousterhout द्वारा [A Philosophy of Software Design](https://web.stanford.edu/~ouster/cgi-bin/book.php) पुस्तकबाट लिइएको हो। यो पुस्तकले सफ्टवेयर विकासमा जटिलताको सार मात्र होइन, तर Parnas को प्रभावशाली पेपर [On the Criteria To Be Used in Decomposing Systems into Modules](https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf) को उत्कृष्ट व्याख्या पनि गर्छ। दुवै आवश्यक पठनहरू हुन्। अन्य सम्बन्धित पढाइहरू: [A Philosophy of Software Design vs Clean Code](https://github.com/johnousterhout/aposd-vs-clean-code), [It's probably time to stop recommending Clean Code](https://qntm.org/clean), [Small Functions considered Harmful](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29)।
<details>
<summary><b>महत्त्वपूर्ण चीजहरू ठूला हुनुपर्छ, उदाहरणहरू</b></summary>
<br>
<div align="center">
<img src="/img/dirty.png" alt="Clean vs Dirty" width="600">
</div>
<blockquote>यदि तपाईंले आफ्नो महत्त्वपूर्ण "क्रक्स" फङ्क्सनहरूलाई ठूलो ("फोहोर") हुन दिनुभयो भने, तिनीहरूलाई फङ्क्सनको समुद्रबाट छान्न सजिलो हुन्छ, तिनीहरू स्पष्ट रूपमा महत्त्वपूर्ण छन्: केवल तिनीहरूलाई हेर्नुहोस्, तिनीहरू ठूला छन्!</blockquote>
यो तस्बिर Carson Gross द्वारा <a href="https://htmx.org/essays/codin-dirty/" target="_blank">Codin' Dirty</a> लेखबाट लिइएको हो। तपाईंले त्यहाँ गहिरो फङ्क्सनहरूको <a href="https://htmx.org/essays/codin-dirty/#real-world-examples" target="_blank">वास्तविक विश्व उदाहरणहरू</a> फेला पार्नुहुनेछ।
</details>
P.S. यदि तपाईंलाई लाग्छ कि हामी धेरै जिम्मेवारीहरू भएका फुलाएको God objects को लागि समर्थन गर्दैछौं, तपाईंले यसलाई गलत बुझ्नुभयो।
## एउटा कुराको लागि जिम्मेवार
प्रायः, हामी धेरै उथला मोड्युलहरू सिर्जना गर्छौं, केही अस्पष्ट "एक मोड्युल एक, र केवल एक, कुराको लागि जिम्मेवार हुनुपर्छ" सिद्धान्त पछ्याउँदै। यो धमिलो एक कुरा के हो? एक वस्तु तत्काल बनाउनु एक कुरा हो, होइन? त्यसोभए [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks) ठीक जस्तो देखिन्छ। **यस्ता क्लासहरूको नाम र इन्टरफेसहरू तिनीहरूको सम्पूर्ण कार्यान्वयन भन्दा मानसिक रूपमा बढी कर लाग्दछ, त्यो कस्तो प्रकारको अमूर्तता हो?** केही गलत भयो।
हामी हाम्रा प्रयोगकर्ताहरू र सरोकारवालाहरूलाई सन्तुष्ट पार्न हाम्रो प्रणालीमा परिवर्तनहरू गर्छौं। हामी तिनीहरूप्रति जिम्मेवार छौं।
> एक मोड्युल एक, र केवल एक, प्रयोगकर्ता वा सरोकारवालाप्रति जिम्मेवार हुनुपर्छ।
यो Single Responsibility Principle को बारेमा हो। सरल शब्दमा, यदि हामीले एक ठाउँमा बग परिचय गर्छौं, र त्यसपछि दुई फरक व्यापार व्यक्तिहरू गुनासो गर्न आउँछन्, हामीले सिद्धान्तको उल्लंघन गरेका छौं। यसको हाम्रो मोड्युलमा गर्ने चीजहरूको संख्यासँग कुनै सरोकार छैन।
तर अहिले पनि, यो नियमले राम्रो भन्दा बढी हानि गर्न सक्छ। यो सिद्धान्तलाई व्यक्तिहरू जति छन् त्यति फरक तरिकाले बुझ्न सकिन्छ। राम्रो दृष्टिकोण यो सबैले कति संज्ञानात्मक भार सिर्जना गर्छ हेर्नु हुनेछ। एक ठाउँमा परिवर्तनले विभिन्न व्यापार धाराहरूमा प्रतिक्रियाहरूको श्रृंखला ट्रिगर गर्न सक्छ भनेर सम्झनु मानसिक रूपमा माग गर्ने छ। र त्यो यति हो, सिक्न कुनै फ्यान्सी शब्दहरू छैनन्।
## धेरै उथला माइक्रोसर्भिसहरू
यो उथले-गहिरो मोड्युल सिद्धान्त स्केल-अग्नोस्टिक हो, र हामी यसलाई माइक्रोसर्भिस आर्किटेक्चरमा लागू गर्न सक्छौं। धेरै उथला माइक्रोसर्भिसहरूले कुनै राम्रो गर्दैनन् - उद्योग केही हदसम्म "म्याक्रोसर्भिस" तिर अगाडि बढिरहेको छ, अर्थात्, सेवाहरू जुन त्यति उथले छैनन् (=गहिरो)। सबैभन्दा खराब र समाधान गर्न गाह्रो घटनाहरू मध्ये एक तथाकथित वितरित मोनोलिथ हो, जुन प्रायः यो अत्यधिक दानेदार उथले विभाजनको परिणाम हो।
मैले एक पटक एक स्टार्टअपमा परामर्श गरें जहाँ पाँच विकासकर्ताहरूको टोलीले १७(!) माइक्रोसर्भिसहरू प्रस्तुत गरे। तिनीहरू १० महिना पछाडि थिए र सार्वजनिक रिलीजको नजिक कतै पनि देखिँदैनथे। प्रत्येक नयाँ आवश्यकताले ४+ माइक्रोसर्भिसहरूमा परिवर्तनहरू निम्त्यायो। यस्तो वितरित प्रणालीमा समस्या पुन: उत्पादन र डिबग गर्न ठूलो समय लाग्यो। बजार र संज्ञानात्मक भार दुवै समय अस्वीकार्य रूपमा उच्च थिए। `🤯`
के यो नयाँ प्रणालीको अनिश्चितता सम्बोधन गर्ने सही तरिका हो? सुरुमा सही तार्किक सीमाहरू प्राप्त गर्न अत्यन्तै गाह्रो छ। कुञ्जी भनेको तपाईंले जिम्मेवारीपूर्वक पर्खन सक्ने गरी ढिलो निर्णयहरू गर्नु हो, किनभने त्यो तपाईंसँग सबैभन्दा बढी जानकारी हुने समय हो। अगाडि नेटवर्क तह प्रस्तुत गरेर, हामी हाम्रो डिजाइन निर्णयहरू सुरुदेखि नै उल्टाउन गाह्रो बनाउँछौं। टोलीको एक मात्र औचित्य थियो: "FAANG कम्पनीहरूले माइक्रोसर्भिस आर्किटेक्चर प्रभावकारी साबित गरे"। *नमस्ते, तपाईंले ठूलो सपना देख्न बन्द गर्नुपर्छ।*
[Tanenbaum-Torvalds debate](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate) ले तर्क गर्यो कि Linux को मोनोलिथिक डिजाइन त्रुटिपूर्ण र अप्रचलित थियो, र यसको सट्टा माइक्रोकर्नेल आर्किटेक्चर प्रयोग गर्नुपर्छ। वास्तवमा, माइक्रोकर्नेल डिजाइन "सैद्धान्तिक र सौन्दर्य" दृष्टिकोणबाट श्रेष्ठ जस्तो देखिन्थ्यो। व्यावहारिक पक्षम
================================================
FILE: README.prompt.md
================================================
You are an engineer who writes code for **human brains, not machines**. You favour code that is simple to understand and maintain. Remember at all times that the code you will be processed by human brain. The brain has a very limited capacity. People can only hold ~4 chunks in their working memory at once. If there are more than four things to think about, it feels mentally taxing for us.
Here's an example that's hard for people to understand:
```
if val > someConstant // (one fact in human memory)
&& (condition2 || condition3) // (three facts in human memory), prev cond should be true, one of c2 or c3 has be true
&& (condition4 && !condition5) { // (human memory overload), we are messed up by this point
...
}
```
A good example, introducing intermediate variables with meaningful names:
```
isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
// (human working memory is clean), we don't need to remember the conditions, there are descriptive variables
if isValid && isAllowed && isSecure {
...
}
```
- Don't write useless "WHAT" comments, especially the ones that duplicate the line of the following code. "WHAT" comments only allowed if they give a bird's eye overview, a description on a higher level of abstraction that the following block of code. Also, write "WHY" comments, that explain the motivation behind the code (why is it done in that specific way?), explain an especially complex or tricky part of the code.
- Make conditionals readable, extract complex expressions into intermediate variables with meaningful names.
- Prefer early returns over nested ifs, free working memory by letting the reader focus only on the happy path only.
- Prefer composition over deep inheritance, don’t force readers to chase behavior across multiple classes.
- Don't write shallow methods/classes/modules (complex interface, simple functionality). An example of shallow class: `MetricsProviderFactoryFactory`. The names and interfaces of such classes tend to be more mentally taxing than their entire implementations. Having too many shallow modules can make it difficult to understand the project. Not only do we have to keep in mind each module responsibilities, but also all their interactions.
- Prefer deep method/classes/modules (simple interface, complex functionality) over many shallow ones.
- Don’t overuse language features, stick to the minimal subset. Readers shouldn't need an in-depth knowledge of the language to understand the code.
- Use self-descriptive values, avoid custom mappings that require memorization.
- Don’t abuse DRY, a little duplication is better than unnecessary dependencies.
- Avoid unnecessary layers of abstractions, jumping between layers of abstractions (like many small methods/classes/modules) is mentally exhausting, linear thinking is more natural to humans.
================================================
FILE: README.pt-br.md
================================================
# Carga Cognitiva é o que importa
[Prompt](README.prompt.md) | [Versão do Blog](https://minds.md/zakirullin/cognitive) | [Original](README.md)
*Este é um documento vivo, última atualização da tradução: **Janeiro de 2026** e última atualização do documento original: **Outubro de 2025**. Contribuições são bem vindas!*
## Introdução
Extistem muitas palavras na moda e "melhores práticas" por aí, mas maior parte delas tem falhado. Elas falharam porque foram imaginadas, ao invés de se basearem na realidade. Essas ideias foram baseadas na estética e julgamentos subjetivos. Precisamos de algo mais fundamental, algo que não possa estar errado.
Algumas vezes sentimos uma confusão ocorrendo no código. Confusão que custa tempo e dinheiro. Confusão esta, causada pela *alta carga cognitiva*. Este não é apenas algum conceito abstrato chique, mas uma **limitação fundamental humana**. Não é imaginado, é algo que está lá e podemos sentir.
Já que gastamos muito mais tempo lendo e compreendendo código que escrevendo, poderíamos constantemente nos questionar se estamos embarcando carga cognitiva excessiva em nosso código.
## Carga Cognitiva
> Carga cognitiva é o quanto um desenvolvedor precisa pensar para completar uma tarefa.
Quando lê-se o código, você coloca coisas como valores de variáveis, controles de fluxo e sequências de chamadas em sua cabeça. A pessoa média consegue guardar aproximadamente [quatro desses blocos](https://github.com/zakirullin/cognitive-load/issues/16) em sua memória de trabalho. Uma vez que a carga cognitiva atinge tal limite, se torna muito mais difícil de compreender as coisas.
*Digamos que fomos pedidos para fazer alguns consertos em um projeto completamente não-familiar. Disseram-nos que um desenvolvedor realmente inteligente tem contribuído para o mesmo. Muitas arquiteturas legais, bibliotecas fantásticas e tecnologias novas e da moda foram usadas. Em outras palavras, **O autor criou uma alta carga cognitiva deixada para nós.***
<div align="center">
<img src="/img/cognitiveloadv6.png" alt="Cognitive load" width="750">
</div>
Deveríamos reduzir a carga cognitiva em nossos projetos o máximo possível.
<details>
<summary><b>Carga cognitiva e interrupções</b></summary>
<img src="img/interruption.jpeg"><br>
</details>
> Usaremos "carga cognitiva" de maneira informal; por vezes alinhará com o conceito científico específico de Carga Cognitiva, mas não sabemos o suficiente quando alinhará ou não.
## Tipos de carga cognitiva
**Intrínseca** — causada pela dificuldade inerente da tarefa. Não pode ser reduzido, já que se está na cerne do desenvolvimento do *software*.
**Extrínseca** — criada pela forma que a informação é apresentada. Causada por fatores não diretamente relevantes à tarefa, como as peculiaridades do autor esperto. Pode ser largamente reduzida. Focaremos nesse tipo de carga cognitiva.
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="Intrínseca vs Extrínseca" width="600">
</div>
Pularemos direto aos exemplos práticos e concretos de carga cognitiva extrínseca.
---
Referiremos ao nível de carga coginitiva como o seguinte:
- `🧠`: memória de trabalho fresca, nenhuma carga coginitiva.
- `🧠++`: dois fatos em nossa memória de trabalho, carga cognitiva aumentada.
- `🤯`: sobrecarga cognitiva, mais de 4 fatos
> Nosso cérebro é muito mais complexo e não explorado, mas podemos seguir neste modelo simplístico.
## Condicionais complexas
```go
if val > someConstant // 🧠+
&& (condition2 || condition3) // 🧠+++, condição prévia deve ser verdadeira, uma de c2 ou c3 tem de ser verdadeira
&& (condition4 && !condition5) { // 🤯, estamos confusos neste momento
...
}
```
Introduza variáveis intermediárias com nomes significativos:
```go
isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
// 🧠, não precisamos lembrar as condições, elas são variáveis descritivas
if isValid && isAllowed && isSecure {
...
}
```
## Ifs aninhados
```go
if isValid { // 🧠+, ok, código aninhado se aplica a entradas válidas, apenas
if isSecure { // 🧠++, fazemos a tarefa apenas para entradas válidas e seguras
stuff // 🧠+++
}
}
```
Compare agora com *early returns*:
```go
if !isValid
return
if !isSecure
return
// 🧠, não precisamos ligar por conta dos retornos breves. Se estamos aqui, então tudo certo
stuff // 🧠+
```
Podemos apenas focar no caminho feliz, liberando nossa memória de trabalho de todos esses tipos de pré-condições.
## Pesadelo de herança
Fomos pedidos para mudar algumas coisas para nossos usuários administradores: `🧠`
`AdminController extends UserController extends GuestController extends BaseController`
Ahh, parte da functionalidade está em `BaseControler`, vamos dar uma olhada: `🧠+`
O mecanismo básico foi introduzido em `GuestController`: `🧠++`.
Algumas coisas foram parcialmente alteradas em `UserController`: `🧠+++`.
Finalmente, estamos aqui, `AdminController`, vamos programar nossa tarefa! `🧠++++`.
Ei, espera! Existe um `SuperuserController` que extende `AdminController`. Ao modificar `AdminController`, podemos quebrar partes da classe herdeira, vamos mergulhar em `SuperuserController` primeiro: `🤯`
Prefira composição à herança. Não vamos entrar em muitos detalhes — existem uma [variedade de materiais](https://www.youtube.com/watch?v=hxGOiiR9ZKg) por aí.
## Pequenos métodos, classes ou módulos demasiados
> Método, classes e módulos são intercambiáveis neste contexto.
Mantras como "métodos deveriam ser menores que 15 linhas de código" ou "classes deveriam ser pequenas" se tornaram em algo errado.
**Módulo profundo** — interface simples, funcionalidade complexa
**Módulo raso** — interface relativamente complexa comparada à pequena funcionalidade que isso provê.
<div align="center">
<img src="/img/deepmodulev8.png" alt="Módulo Profundo" width="700">
</div>
Ter muitos módulos rasos pode tornar o projeto difícil de compreender. **Não apenas temos de manter em mente a responsabilidade de cada módulo, como também suas interações**. Para compreender o propósito de um módulo raso vamos precisar olhar para a funcionalidade de todos os módulos relacionados. Pular entre cada componente raso é mentalmente exaustivo <a target="_blank" href="https://blog.separateconcerns.com/2023-09-11-linear-code.html">pensamento linear</a> é mais natural para nós, humanos.
> Ocultar informação é fundamental. Não precisamos ocultar tanto a complexidade em módulos rasos.
Eu tenho dois projetos pet. Ambos com algo entre 5 mil linhas de código. O primeiro tem 80 classes rasas, enquanto o segundo tem apenas 7 classes profundas. Eu não tenho mantido nenhum dos dois projetos por um ano e meio.
Ao retornar, eu percebi o quão extremamente difícil é para desembaraçar todas as interações entre essas 80 classes do primeiro projeto. Eu teria de reconstruir a quantidade enorme de carga coginitiva antes que pudesse começar a programar. Por outro lado, eu fui capaz de compreender o segundo projeto rapidamente, já que tinham apenas algumas classes profundas com interfaces simples.
> Os melhores componentes são aqueles que provém funcionalidades poderosas enquanto mantém interfaces simples.
>
> *John Ousterhout, Um Filósofo de Deisgn de Software*
A interface do *UNIX I/O* é bastante simples. Ele tem apenas cinco básicas chamadas.
```py
open(caminho, bandeiras, permissões)
read(da, buffer, contagem)
write(da, buffer, contagem)
lseek(da, desvio, posiçãoDeReferencia)
close(da)
```
Uma implementação moderna dessa interface tem **centeras de milhares de linhas de código**. Muitas das complexidades estão ocultas por debaixo do capô. Ainda assim, é fácil de usar devido a sua simples interface.
> Esse exemplo de módulo profundo é tirado do livro *[A Philosophy of Software Design](https://web.stanford.edu/~ouster/cgi-bin/book.php)* por John Ousterhout. Não apenas este livro cobre bastante a essência da complexidade no desenvolvimento de *Software*, mas também tem a melhor interpretação do artigo influencial de Parnas *[On the Criteria To Be Used In Decomposing Systems into Modules](https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf)*. Ambos são essenciais de ler. Outras leituras relacionadas: *[A Philosophy of Software Design vs Clean Code](https://github.com/johnousterhout/aposd-vs-clean-code)*, *[It's probably time to stop recommending Clean Code](https://qntm.org/clean)*, *[Small Functions considered Harmful](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29)*.
<details>
<summary><b>Coisas importantes deviam ser grandes, exemplos</b></summary>
<br>
<div align="center">
<img src="/img/dirty.png" alt="Clean vs Dirty" width="600">
</div>
<blockquote>
Se você permitir seu "crux" de funções serem maiores, sujas, será mais fácil de escolhê-las dentro de um mar de funções, elas serão obviamente importantes: apenas olhe para elas, já que grandes!
</blockquote>
Esta imagem foi tiradas de <a href="https://htmx.org/essays/codin-dirty/" target="_blank">Codin' Dirty</a>, artigo por Carson Gross. Você encontrará <a href="https://htmx.org/essays/codin-dirty/#real-world-examples" target="_blank"> exemplos do mundo real</a> de funções profundas aí.
</details>
P.S. Caso pense que estamos enraizando objetos divinos inchados com muitas responsabilidades, você compreendeu errado.
## Responsável por uma única coisa
Frequentemente, acabamos por criar muitos módulos rasos, seguindo vagos princípios como "um módulo deveria ser responsável por uma coisa, e apenas uma única coisa". O que significa isso? Instanciar um objeto é uma única coisa, certo? Então [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks) parece ser correto. **Os nomes e interfaces destas classes tendem a ser mentalmente desgastantes. Mais que sua implementação completa. Que tipo de abstração é essa?** Algo está errado.
Fazemos mudanças em nossos sistemas para satisfazer nossos usuários e *Stakeholders*. Somos responsáveis por eles.
> Um módulo deveria ser responsável por um, e apenas um, usuário ou *Stakeholder*.
Isso é o que o *Princípio da Responsabilidade Única* (*Single Responsibility Principle*) é sobre. Em termos simples, se introduzirmos um bug em um lugar e, em seguida, dois profissionais de negócios diferentes vierem reclamar, teremos violado o princípio. Isso não tem nada a ver com o número de coisas que fazemos em nosso módulo.
Mas ainda assim, esta regras pode ser mais danosa que boa. Esse princípio pode ser compreendido de múltiplas formas como há de pessoas na terra. Uma forma melhor seria a de olhar para a quantidade de carga cognitiva que ele pode criar. Esta é a demanda mental para lembrar que mudar em um lugar pode ativar uma cadeia de reações em diferentes fluxos de negócios.
## Muitos microsserviços rasos
Esse princípio de módulo raso-profundo é agnóstico à escala, e podemos aplicá-lo à arquitetura de microsserviços. Uma quantidade excessiva de microsserviços rasos não traz nenhum benefício — a indústria tem caminhado em direção a "macrosserviços", exemplo, serviços que não são tão rasos (=profundos). Um dos piores fenômenos e mais dificéis de consertar é o chamado monolito distribuído, o qual é frequentemente resultado de uma separação rasa excessivamente granular.
Certa vez eu consultei uma *Startup* onde um time de cinco desenvolvedores introduziram 17 (!) microsserviços. Eles estavam 10 meses atrasados e nem um pouco próximo do lançamento público. Cada novo requisito levava a mudanças em mais de 4 microsserviços. Isso levou uma quantidade enorme de tempo para reproduzir e depurar um problema como um sistema distribuído. Ambos, o estresse com prazos e a carga coginitiva estavam inaceitavelmente altos. `🤯`
Esta é a forma correta de lidar com a incerteza de um novo sistema? É extremamente difícil eleger os limites lógicos no início. A chave é tomar decições o mais tarde que você responsavelmente possa esperar, pois terá a informação nas mãos. Ao introduzir uma camada de network logo de cara, fazemos nossa decisão de *design* difícil de reverter logo do início. A única justificativa do time foi: "Empresas FAANG provaram que a arquitetura de microsserviços é efetiva". *Olá, você precisa parar de sonhar alto.*
O [debate Tanenbaum-Torvalds](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate) argumentou que o design monolítico do Linux era falho e obsoleto, e que a arquitetura de *microkernel* deveria ser usada ao invés. De fato, o *design* de *microkernel* parecia ser superior do ponto de vista "teorético e estético". No lado prático das coisas — três décadas depois, o GNU Hurd baseado em *microkernel* continua em desenvolvimento, e o Linux monolítico está em todo canto. Essa página é entregue por um Linux, seu bule inteligente utiliza Linux. Linux monolito.
Um monolito bem-feito com módulos verdadeiramente isolados é frequentemente muito mais flexível que um monte de microsserviços. Ele requer muito menos esfoço cognitivo para manter. Apenas quando separar *deployments* se torna crucial, como escalar o desenvolvimento de time, que devemos considerar adicionar uma camada de *network* entre os módulos, futuro microsserviços.
## Linguagem rica de recursos
Ficamos ansiosos por novos recursos lançados em nossas linguagem favoritas. Gastamos certo tempo para aprender esses recursos, e construímos código em cima disso.
Se existem vários recursos, podemos gastar meia hora brincando com algumas linhas de código para usar um ou outro recurso. E isso é meio que uma perda de tempo. Mas o que é pior, **quando retornar depois, você poderá ter de recriar todo o processo de pensamento!**
**Você não apenas tem de compreender esse programa complicado, como tem de compreender porque um programador decidiu essa forma de resolver o problem com os recursos disponíveis**. `🤯`
Essas afirmações foram feitas por ninguém menos que Rob Pike.
> Reduza a carga cognitiva pelo número de escolhas.
Recursos de linguagem são OK, contanto que sejam ortoginais entre si.
<details>
<summary><b>Pensamentos de um engenheiro com 20 anos de experiência em C++ ⭐️</b></summary>
<br>
Eu estava procurando em meu leitor de RSS outro dia e notei que eu tenho algo entre trezentos artigos sobre linguagens desde o último verão, e eu me sinto bem!
<br><br>
Eu tenho usado C++ por 20 anos, o que são quase dois terços de minha vida. Maior parte de minha experiência deriva dos cantos mais obscuros da linguagem (como <i>undefined behavior</i> de todos os tipos). Isso não é apenas uma experiência reusável, commo é o tipo de coisa assustadora para se jogar fora.
<br><br>
Tipo, você pode imaginar, o token <code>||</code> tem significado distinto em <code>requires ((!P<T> || !Q<T>))</code> e em <code>requires (!(P<T> || Q<T>))</code>. A primeira é a disjunção de restrição, a segunda é o bom e velho operador lógico OR, e elas se comportam de maneira diferente.<br><br>
Você não consegue alocar espaço para um tipo trivial e apenas chamar <code>memcpy</code> para um conjunto de bytes sem esforço extra — isso não iniciará o ciclo-de-vida do objeto. Este foi o caso antes do C++20. E foi consertado no C++20, mas a carga cognitiva da linguagem tem apenas aumentado.<br><br>
A carga cognitiva está constantemente crescendo, mesmo quando as coisas são consertadas. Eu deveria saber o que foi consertado, quando isso foi consertado e como era antes. Eu sou um profissional afinal de contas. Certo, C++ é bom para suporte legado, o que significa que você <b>irá enfrentar</b> o legado. Por exemplo, último mês um colega meu me perguntou sobre o comportamento em C++03. <code>🤯</code><br><br>
Existiram 20 formas de inicialização. A sintaxe uniforme de inicialização foi adicionado. Agora temos 21 formas de inicialização. De qualquer forma, alguém lembra as regras para selecionar construtores de uma lista inicializadora? Algo sobre conversão implícita com o mínimo de perda de informação <i>mas se</i> o valor é conhecido estaticamente, então... <code>🤯</code><br><br>
<b>
Esse aumento na carga cognitiva não é causado pela tarefa em mãos. E não é uma complexidade intrínseca do domínio. É apenas algo que está lá devido a questões históricas</b> (<i>Carga cognitiva extrínseca</i>).<br><br>
Eu tive de criar algumas regras. Assim, se uma linha de código não for óbvia e eu tivesse de lembrar do <i>Standard</i>, é melhor não escrever dessa forma. O <i>Standard</i> é longo, de 1500 páginas, a propósito.<br><br>
<b>De nenhuma forma estou tentando julgar C++.</b> Amo a linguagem. Mas estou apenas cansado por agora.<br><br>
<p>Obrigado ao <a href="https://0xd34df00d.me" target="_blank">0xd34df00d</a> por escrever.</p>
</details>
## Lógica de negócios e código de status HTTP
No backend, retornamos:
- `401` para tokens JWT expirados
- `403` para acesso insuficiente
- `418` para usuários banidos
Os engenheiros no frontend usam a API para implementar a funcionalidade de login. Eles precisariam de temporariamente criar a seguinte carga cognitiva em suas cabeças:
- `401` para tokens JWT expirados: `🧠+`, ok vamos temporariamente lembrar disso
- `403` para acesso insuficiente: `🧠++`
- `418` para usuários banidos: `🧠+++`
Desenvolvedores frontend deveriam (com sorte) introduzir algum tipo de dicionário (status numérico → significado) em seu lado, então as gerações subsequentes de contribuidores não precisaram ter de recirar esse mapa em sua mente.
Então os engenheiros QA entram na jogada:
"Ei, eu tenho um status `403`, isso seria o token expirado ou acesso insuficiente?"
**Engenheiros QA não conseguem pular direto aos testes, pois eles primeiro têm de recriar a carga cognitiva que os engenheiros backend uma vez criaram**.
Por que guardar esse mapa customizado em nossa memória de trabalho? É melhor abstrair os nossos detalhes de negócio do protocolo de transferência HTTP e retornar códigos auto-descritíveis no corpo da resposta:
```json
{
"código": "jwt-expirado"
}
```
Carga cognitiva do lado do *front-end*: `🧠` (fresco, nenhum fato é guardado em mente).
Carga cognitiva do lado do *QA*: `🧠`.
As mesmas regras se aplicam a todos os tipos de status numéricos (em bancos de dados ou qualquer outra coisa) — **prefira *strings* auto-descritíveis**. Não estamos em uma era de computadores com 640K de memória para otimizar.
> Pessoas gastam tempo argumentando entre `401` e `403`, tomando decisões baseadas em seus próprios modelos mentais. Novos desenvolvedores estão chegando, e eles precisam de recriar esse processo de pensamento. Você pode ter documentado os Porquês (ADRs) para o seu código, ajudando novatos a compreender as decisões feitas. Mas no final apenas não fazem sentido. Nós podemos separar erros entre ambos, relacionados-ao-usuário ou relacionados-ao-servidor, mas além disso, as coisas são muito obscuras.
P.S. Muitas vezes, é mentalmente cansativo distinguir entre “autenticação” e “autorização”. Podemos usar termos mais simples, como [“login” e “permissões”](https://ntietz.com/blog/lets-say-instead-of-auth/), para reduzir a carga cognitiva.
## Abusando do princípio DRY
Não se repita (Don't Repeat Yourself, DRY) — é um dos primeiros princípios que você é ensinado como um engenheiro de *software*. Está tão profundamente embarcado em nós que não podemos aguentar o fato de algumas linhas extras de código. A pesar de ser, em geral, uma regra boa e fundamental, quando sobre-usada, leva a uma carga cognitiva que não poodemos suportar.
Hoje em dia, todo mundo contrói *software* baseado em componentes logicamente separados. Frequentemente, são distribuídos entre múltiplas bases de código representando serviços separados. Quando você tenta eliminar qualquer repetição, você pode acabar por criar um acoplamento estreito entre componentes não relacionados. Como resultado, mudanças em uma parte pode levar a consequências não intencionais em outras áreas aparentemente não relacionadas. Isso também pode atrapalhar a capacidade de trocar ou modificar componentes individuais sem impactar em sistemas completos. `🤯`
De fato, o mesmo problema surge mesmo dentro de um único módulo. Você pode extrair funcionalidades comuns muito cedo, baseado em similaridades pecebidas que podem não realmente existir no longo prazo. Isso pode resultar em abstrações desnecessárias que são difíceis de estender ou modificar.
Rob Pike disse, certa vez:
> Uma pequena cópia é melhor que uma pequena dependência.
Somos tentados a não re-inventar a roda tão fortemente que estamos prontos para importar biliotecas grandes e pesadas para usar pequenas funções que poderíamos escrever nós mesmos.
**Todas as suas dependências são seu código**. Indo através de mais de 10 níveis de *Stack Trace* de alguma biblioteca importada e interpretar o que está de errado (*porque as coisas dão errado*) e é doloroso.
## Estreitamente acoplado a um *Framework*
Existem várias mágicas em *frameworks*. Ao depender muito de *frameworks*, **nós forçamos todos os desenvolvedores a aprender essa "mágica" primeiro**. Isso pode levar meses. A pesar disso, *frameworks* nos permitem criar Produtos Mínimos Viáveis (Minimal Viable Products, MVP) em poucos dias, no longo prazo eles tendem a adicionar complexidade desnecessária e carga cognitiva.
Pior ainda, a algum ponto os *frameworks* podem se tornar uma retrição significativa quando encarar um novo requerimento que não se encaixa na arquitetura. Até que as pessoas acabem por bifurcar um *framework* e manter suas próprias versões customizadas. Imagine a quantidade de carga cognitiva que um novato teria de construir (por exemplo, aprender esse *framework* customizado) para que possa entregar algum valor. `🤯`
**De nenhuma forma eu advogo em pró de inventar tudo do zero!**
Podemos programar de uma forma agnóstica à *frameworks*. A regra de negócios não deveriam residir dentro de um *framework*; ao invés disso, deveriamos usar os componentes do *framework*. Use *framework* no estilo de bibliotecas. Dessa forma, permitimos que novos contribuidores adicionem valor do dia um, sem necessidade de mergulhar em detritos de complexidade relacionadas ao *framework* primeiro.
> [Why I Hate Frameworks](https://minds.md/benji/frameworks)
## Arquitetura em camadas
Há uma certa emoção de engenharia em tudo isso.
Eu era um apaixonado advogado de arquitetura Hexagonal/Onion por anos. Usei e encorajei outros times a usar também. A complexidade de nossos projetos aumentaram, a quantidade de arquivos sozinhos dobraram. Parece como se eu estivesse escrevendo um monte de código-cola. A cada mudança de requerimento, temos de fazer mudanças entre múltiplas camadas de abstração, o que se torna tedioso. `🤯`
**Abstrações são supostamente para ocultar complexidade, não adicionar [indireção](https://fhur.me/posts/2024/thats-not-an-abstraction)**. Pulando de chamada em chamada para ler e compreender o que há de errado e qual é o requerimento vital para rapidamente resolver o problema. Com essa arquitetura em camada, desacoplar requer um fator extra exponencial, frequentemente desarticulado, para chegar ao ponto onde a falha ocorre. Cada *stack trace* ocupa espaço em nossa limitada memória de trabalho. `🤯`
Essa arquitetura foi algo que fez sentido intuitivo de primeira, mas cada vez que tentamos aplicá-la a projetos, isso fez mais mal que bem. Gastamos anos em atividade mental desnecessária e escrevemos código-cola inútil sem valor de negócios claro. Ao contrário, tornamos as coisas piores para os negócios ao forçar novatos a aprender nossas escolhas (modelos mentais) primeiro. O tempo de mercado tem piorado. E no final, desistimos em favor do bom e velho princípio de inversão de dependência. **Nenhum termo de porta ou adaptador para aprender, nenhuma camada horizontal de abstração, nenhuma carga coginitiva extrínseca**.
<details>
<summary><b>Princípios de programação e experiência</b></summary>
<img src="img/complexity.png"><br>
<a href="https://twitter.com/flaviocopes">@flaviocopes</a>
</details>
Se você pensa que tal camadas irão te permitir rapidamente trocar o banco de dados ou quaisquer outras dependências, você está enganado. Mudar o armazenamento causa vários problemas, e acredite em nós, ter algumas abstrações para acessar a camada de dado é a última de suas preocupações. Ao menos, abstrações podem salvar algo entre 10% do nosso tempo de migração (se houver), a dor real está em incompatibilidades do modelo de dado, protocolo de comunicações, desafios de sistemas distrbuídos e [interfaces implícitas](https://www.hyrumslaw.com).
> Com um número suficiente de usuários de uma API,
> não importa qual a sua promessa no contrato:
> todos os comportamentos de seu sistema
> será dependente de alguém.
Fazemos uma migração de armazenamento, e levamos algo entre 10 meses. O sistema legado era *single-thread*, então os eventos expostos eram sequenciais. Todos os nossos sistemas dependem naquele comportamento observado. Esse comportamento não era parte de nosso contrato de API, e não refletia em nosso código. Um novo armazenamento distribuído não nos garantiu isso — os eventos chegam fora-de-ordem. Gastamos apenas algumas horas programando o novo adaptador do armazenamento, agradecimentos a uma abstração. **Gastamos os próximos 10 meses lidando com eventos fora-de-ordem e outros desafios**. Agora é engraçado dizer que abstrações nos ajudam a trocar componentes rapidamente.
**Então, porque pagar o preço de carga cognitiva como uma arquitetura em camadas, se podemos não pagar no futuro?** Adicionalmente, na maioria dos casos, o futuro de trocar algum componente principal nunca acontece.
Essas arquiteturas não são fundamentais, elas são apenas subjetivas, com consequências enviesadas de princípios mais fundamentais. Por que confiar nessas interpretações subjetivas? Siga regras fundamentais ao invés disso: princípio de invesão de dependência, única fonte de verdade, carga cognitiva e ocultação de informação. Nossa lógica de negócios não deveriam depender de módulos de baixo-nível, como banco de dados, interfaces de usuário ou *frameworks*. Deveriamos ser capazes de escrever testes para nossa lógica principal sem ter de nos perocupar com infraestrutura, e é isso. [Dicussão](https://github.com/zakirullin/cognitive-load/discussions/24)
Não adicione camadas de abstração por conta da arquitetura. Adicione-as quando precisar de uma extensão a algum ponto como é justificável por razões práticas.
**[Camadas de abstrações não são livres de mudanças](https://blog.jooq.org/why-you-should-not-implement-layered-architecture), elas são guardadas em nossa limitada memória de trabalho.**
<div align="center">
<img src="/img/layers.png" alt="Camadas" width="400">
</div>
## *Domain-drive design*
*Domain-driven design* (DDD) tem alguns pontos positivos, apesar disso, é frequentemente mal-interpretado. Pessoas dizem "Nós escrevemos código DDD", o qual é um pouco estranho, já que DDD é mais sobre o espaço do problema que o espaço de solução.
Linguagem ubíqua, domínio, contexto adjunto, agregado, *event storming* são todos espaços de problemas. Eles são para nos ajudar a aprender os *insights* sobre o domínio e extrarir seus limites. DDD permite desenvolvedores, especialistas de domínio e pessoas de negócios se comunicarem de forma efetiva ao utilizar uma linguagem unificada e singular. Ao invés de focar nesses aspectos do DDD sobre o espaço do problrma, tendemos a dar ênfase em estruturas de arquivos em particular, serviços, repositórios e outras técnicas de solução de espaço.
Chances são que a forma como interpretamos DDD é única e subjetiva. E se construirmos código em cima dessa compreensão, por exemplo, se criamos uma carga cognitiva extrínseca, desenvolvedores futuros estão condenados. `🤯`
*Team Topologies* provê um *framework* muito melhor e fácil de compreender que nos permite dividir a carga cognitiva entre times. Engenheiros tendem a desenvolver modelos mentais similares depois de aprender sobre *Team Topologies*. DDD, por outro lado, parece estar criando 10 modelos mentais diferentes para 10 leitores distintos. Ao invés de um chão comum, isso se torna um campo de batalha para debates desnecessários.
## Carga Cognitiva em projetos familiares
> O problema está no fato que **familiaridade não é o mesmo que simplicidade.** Eles *parecem* a mesma coisa — mesma facilidade de se mover no espaço sem muito esforço mental — mas por razões muito diferentes. Cada truque "esperto" (leia-se: “autoindulgente”) e não idiomático que você usa acarreta uma penalidade de aprendizagem para todos os outros. Uma vez que eles aprendem, é mais fácil trabalhar com o código. Então é difícil reconhecer como é difícil simplificar um código já familiarizado. É por isso que eu peço aos "novatos" que critiquem o código antes que fiquem muito institucionalizados!
>
> É como se o(s) autor(es) anterior(res) tivesse(m) criado essa grande bagunça um incremento por vez, e não tudo de uma só vez. Então você é a primeira pessoa que tentou encontrar sentido nisso tudo de uma só vez.
>
> Em minha aula, eu descrevo um extenso procedimento SQL armazenado onde estávamos analisando um dia, com centenas de linhas de condicionais em uma grande cláusula WHERE. Alguém me perguntou como eu pude deixar isso ficar assim tão ruim. E disse: "Quando há apenas 2 ou 3 condicionais, adicionar mais uma não faz qualquer diferença. Mas com o tempo, você terá 20 ou 30 condicionais, e adicionar outras não fará nenhuma diferença!"
>
> Não existe "força de simplificação" atuando no código-base outras além de escolhas deliberadas que você faz. Simplificar demanda esforço. E pessoas, muitas vezes, estão com pressa.
>
>*Obrigado ao [Dan North](https://dannorth.net) por seu comentário*.
Se tiver internalizado os modelos mentais do projeto em sua memória de longo-termo, não experienciará a carga cognitiva.
<div align="center">
<img src="/img/mentalmodelsv15.png" alt="Modelos mentais" width="700">
</div>
Quanto mais modelos mentais tiver de aprender, mais tempo levará para um novo desenvolvedor entregar valor.
Uma vez que você embarca novas pessoas em seu projeto, tente medir a quantidade de confusão que eles têm (*Pair Programming* pode ajudar). Caso eles estejam confusos por mais de, mais ou menos, 40 minutos de uma só vez, você tem coisas a melhorar em seu código.
Se você mantiver a carga cognitiva baixa, pessoas podem contribuir para seu código-base dentro de algumas poucas horas ao entrar na empresa.
## Exemplos
- Nossa arquitetura é uma aplicação CRUD padrão, [Um monolito Python em cima do Postgres](https://danluu.com/simple-architectures/).
- Como o Instagram escalou para 14 milhões de usuários com [apenas 3 engenheiros](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million).
- As empresas que nos deixaram tipo "Nossa! Esses caras são [inteligentes para caramba](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)" acabaram, na sua maioria, fracassando.
- Uma função que liga todo o sistema. Se você quiser saber como o sistema funciona, [leia isto](https://www.infoq.com/presentations/8-lines-code-refactoring).
Essas arquiteturas são bastante chatas e fáceis de compreender. Qualquer um pode compreendê-las sem muito esforço mental.
Envolva desenvolvedores júniors em suas reviews de arquitetura, eles podem te ajudar a identificar áreas mentalmente exaustivas.
> Sistemas de software são talvez os mais intricados e complexos (em termos de número de tipos distintos de peças) entre as coisas que a humanidade cria.
>
> *Fred Brooks, The Mythical Man-Month*
**Manter software é difícil**, as coisas quebram e precisamos de todo o esforço mental que pudermos economizar. Quanto menos componentes houver no sistema, menos problemas haverá. A depuração também será menos desgastante mentalmente.
> Depurar é duas vezes mais difícil do que escrever o código, em primeiro lugar. Portanto, se você escrever o código da forma mais esperta possível, você, por definição, não será inteligente o suficiente para depurá-lo.
>
> *Brian Kernighan*
Em geral, a mentalidade de "Uau, essa arquitetura parece ótima!" é enganosa. Essa é uma sensação subjetiva de um determinado momento e não diz nada sobre a realidade. Uma abordagem muito melhor é observar as consequências a longo prazo:
- É fácil reproduzir e depurar um problema? Ou você precisa pular entre *call stacks* ou componentes distribuídos, tentando entender tudo na sua cabeça?
- Podemos fazer alterações rapidamente ou há muitas incógnitas e as pessoas têm medo de mexer nas coisas?
- Os novos funcionários podem adicionar recursos rapidamente? Há alguns modelos mentais únicos a serem aprendidos?
> O que são esses modelos mentais únicos? É um conjunto de regras, geralmente uma mistura de DDD/CQRS/Arquitetura Limpa/Arquitetura Orientada a Eventos. Essa é a interpretação do autor sobre as coisas que mais o entusiasmam. Seus próprios modelos mentais subjetivos.
**Carga cognitiva estranha que os outros têm de internalizar.**
Essas questões são muito mais difíceis de rastrear, e as pessoas geralmente não gostam de respondê-las diretamente. Veja alguns dos sistemas de software mais complexos do mundo, aqueles que resistiram ao teste do tempo — Linux, Kubernetes, Chrome e Redis (veja os comentários abaixo). Você não encontrará nada sofisticado neles, são em sua maioria enfadonhos, e isso é bom.
## Conclusão
Imagine por um momento que o que inferimos no segundo capítulo não seja realmente verdade. Se for este o caso, então a conclusão que acabamos de negar, juntamente com as conclusões do capítulo anterior que aceitamos como válidas, também podem não estar corretas.
Você percebeu? Não só você precisa pular de um lado para outro no artigo para entender o significado (módulos superficiais!), como o parágrafo em geral é difícil de entender. Acabamos de criar uma carga cognitiva desnecessária na sua cabeça. **Não faça isso com seus colegas.**
<div align="center">
<img src="/img/smartauthorv14thanksmari.png" alt="O autor espeto" width="600">
</div>
Deveríamos reduzir qualquer carga cognitiva acima. É algo além de instrínseco para o trabalho que fazemos.
---
[LinkedIn](https://www.linkedin.com/in/zakirullin/), [X](https://twitter.com/zakirullin), [GitHub](https://github.com/zakirullin), artemzr(аt)g-yоu-knоw-com
<details>
<summary><b>Comments</b></summary>
<br>
<p><strong>Rob Pike</strong> <i>(Unix, Golang)</i><br>Nice article.</p>
<p><strong><a href="https://x.com/karpathy/status/1872038630405054853" target="_blank">Andrej Karpathy</a></strong> <i>(ChatGPT, Tesla)</i><br>Nice post on software engineering. Probably the most true, least practiced viewpoint.</p>
<p><strong><a href="https://x.com/elonmusk/status/1872346903792566655" target="_blank">Elon Musk</a></strong> <i>(Rockets)</i><br>True.</p>
<p><strong><a href="https://www.linkedin.com/feed/update/urn:li:activity:7277757844970520576/" target="_blank">Addy Osmani</a></strong> <i>(Chrome, the most complex software system in the world)</i><br>I've seen countless projects where smart developers created impressive architectures using the latest design patterns and microservices. But when new team members tried to make changes, they spent weeks just trying to understand how everything fits together. The cognitive load was so high that productivity plummeted and bugs multiplied.</p>
<p>The irony? Many of these complexity-inducing patterns were implemented in the name of "clean code."</p>
<p>What really matters is reducing unnecessary cognitive burden. Sometimes this means fewer, deeper modules instead of many shallow ones. Sometimes it means keeping related logic together instead of splitting it into tiny functions.</p>
<p>And sometimes it means choosing boring, straightforward solutions over clever ones. The best code isn't the most elegant or sophisticated - it's the code that future developers (including yourself) can understand quickly.</p>
<p>Your article really resonates with the challenges we face in browser development. You're absolutely right about modern browsers being among the most complex software systems. Managing that complexity in Chromium is a constant challenge that aligns perfectly with many of the points you made about cognitive load.</p>
<p>One way we try to handle this in Chromium is through careful component isolation and well-defined interfaces between subsystems (like rendering, networking, JavaScript execution, etc.). Similar to your deep modules example with Unix I/O - we aim for powerful functionality behind relatively simple interfaces. For instance, our rendering pipeline handles incredible complexity (layout, compositing, GPU acceleration) but developers can interact with it through clear abstraction layers.</p>
<p>Your points about avoiding unnecessary abstractions really hit home too. In browser development, we constantly balance between making the codebase approachable for new contributors while handling the inherent complexity of web standards and compatibility. </p>
<p>Sometimes the simplest solution is the best one, even in a complex system.</p>
<p><strong><a href="https://x.com/antirez" target="_blank">antirez</a></strong> <i>(Redis)</i><br>Totally agree about it :) Also, what I believe is missing from mentioned "A Philosophy of Software Design" is the concept of "design sacrifice". That is, sometimes you sacrifice something and get back simplicity, or performances, or both. I apply this idea continuously, but often is not understood.</p>
<p>A good example is the fact that I always refused to have hash items expires. This is a design sacrifice because if you have certain attributes only in the top-level items (the keys themselves), the design is simpler, values will just be objects. When Redis got hash expires, it was a nice feature but required (indeed) many changes to many parts, raising the complexity.</p>
<p>Another example is what I'm doing right now, Vector Sets, the new Redis data type. I decided that Redis would not be the source of truth about vectors, but that it can just take an approximate version of them, so I was able to do on-insert normalization, quantization without trying to retain the large floats vector on disk, and so forth. May vector DBs don't sacrifice the fact of remembering what the user put inside (the full precision vector).</p>
<p>These are just two random examples, but I apply this idea everywhere. Now the thing is: of course one must sacrifice the right things. Often, there are 5% features that account for a very large amount of complexity: that is a good thing to kill :D</p>
<p><strong><a href="https://working-for-the-future.medium.com/about" target="_blank">A developer from the internet</a></strong><br>You would not hire me... I sell myself on my track record of released enterprise projects.</p>
<p>I worked with a guy that could speak design patterns. I could never speak that way, though I was one of the few that could well understand him. The managers loved him and he could dominate any development conversation. The people working around him said he left a trail of destruction behind him. I was told that I was the first person that could understand his projects. Maintainability matters. I care most about TCO. For some firms, that's what matters.</p>
<p>I logged into Github after not being there for a while and for some reason it took me to an article in a repository by someone that seemed random. I was thinking "what is this" and had some trouble getting to my home page, so I read it. I didn't really register it at the time, but it was amazing. Every developer should read it. It largely said that almost everything we've been told about programming best practices leads to excessive "cognitive load", meaning our minds are getting kicked by the intellectual demands. I've known this for a while, especially with the demands of cloud, security and DevOps.</p>
<p>I also liked it because it described practices I have done for decades, but never much admit to because they are not popular... I write really complicated stuff and need all the help I can get.</p>
<p>Consider, if I'm right, it popped up because the Github folks, very smart people, though that developers should see it. I agree.</p>
<p><a href="https://news.ycombinator.com/item?id=45074248" target="_blank">Comments on Hacker News</a> (<a href="https://news.ycombinator.com/item?id=42489645" target="_blank">2</a>)</p>
</details>
================================================
FILE: README.tr.md
================================================
# Önemli olan bilişsel yüktür
## Giriş
Etrafta hatrı sayılır sayıda klişe söylemler ve "best practice" yapılar var, fakat bunların çoğu sadece sözde kalmış ve başarısızlıkla sonuçlanmıştır. Bizimse daha temel bir şeye ihtiyacımız var — yanlış olamayacak bir şeye.
Bazen bir kodu okurken kafa karışıklığı yaşarız. Bu karışıklık bize zaman ve para kaybettirir. Bunun nedeni, yüksek **bilişsel yüktür**.
Bu, öyle süslü püslü ve soyut bir kavram değil; aksine, **insan zihninin doğasından gelen temel bir sınırlamadır**. Hayal ürünü değil — gerçekten orada ve hepimiz onu hissediyoruz.
Çünkü kod yazmaya harcadığımız zamandan çok daha fazlasını onu **okumaya** ve **anlamaya** harcıyoruz. Bu yüzden kendimize sürekli şu soruyu sormalıyız:
"**Yazdığımız koda gereğinden fazla bilişsel yük yüklüyor muyuz?**"
## Bilişsel yük
> Bilişsel yük, bir geliştiricinin bir görevi tamamlamak için ne kadar düşünmesi gerektiğidir.
Kod okurken; değişken değerlerini, kontrol akışını ve fonksiyon çağrı dizilerini zihnimizde tutmamız gerekir. Fakat ilginçtir ki, ortalama bir insan, çalışma belleğinde bu türden yalnızca yaklaşık **[dört bilgi parçasını](https://github.com/zakirullin/cognitive-load/issues/16)** tutabilir. Bilişsel yük bu eşiğe ulaştığında, kodu anlamak **ciddi şekilde zorlaşır**.
*Diyelim ki bize tamamen yabancı bir projede bazı hataları düzeltmemiz söylendi. Projeye daha önce çok zeki bir geliştirici katkıda bulunmuş.
Kulağa harika geliyor: karmaşık mimariler, havalı kütüphaneler ve trend teknolojiler kullanılmış.
Yani başka bir deyişle, **yazarı bizim için oldukça yüksek bir bilişsel yük üretmiş**.*
<div align="center">
<img src="img/cognitiveloadv6.png" alt="Cognitive load" width="750">
</div>
*Bu yüzden projelerimizde bilişsel yükü olabildğince azaltmalıyız.*
<details>
<summary><b>Bilişsel yük ve bölünmeler</b></summary>
<img src="img/interruption.jpeg"><br>
</details>
## Bilişsel yükün türleri
**İçsel yük** - Görevin kendi doğasından gelen zorluk. Bu yük azaltılamaz; çünkü yazılım geliştirmenin tam kalbinde yer alır.
**Dışsal yük** - Bilginin sunuluş biçiminden kaynaklanır. Görevle doğrudan ilgisi olmayan ama kafa karıştıran unsurlar yüzünden oluşur — mesela “çok zeki” yazarın kendine has tuhaflıkları gibi. Bu tür yük ciddi şekilde azaltılabilir. Biz de zaten bu tip düşünsel yükü azaltmaya odaklanacağız.
<div align="center">
<img src="img/smartauthorv14thanksmari.png" alt="Intrinsic vs Extraneous" width="600">
</div>
Şimdi doğrudan dışsal bilişsel yükün somut pratik örneklerine geçelim.
---
Bilişsel yük düzeyini şu şekilde ifade edeceğiz:
`🧠`: taze çalışma belleği, sıfır bilişsel yük
`🧠++`: çalışma hafızamızdaki iki yük, bilişsel yükün arttığını gösteriyor
`🤯`: Bilişsel aşırı yükleme, 4'ten fazla yük
> Beynimiz daha karmaşık ve keşfedilmemiş, fakat biz bu basite indirgenmiş modelle devam edebiliriz.
## Komplex koşullar
```go
if val > someConstant // 🧠+
&& (condition2 || condition3) // 🧠+++, önceki koşul doğru olmalı, c2 veya c3'ten biri doğru olmalı
&& (condition4 && !condition5) { // 🤯, bu noktada kafayı yedik
...
}
```
Anlamlı isimlere sahip ara değişkenleri tanıtın:
```go
isValid = val > someConstant
isAllowed = condition2 || condition3
isSecure = condition4 && !condition5
// 🧠, koşulları hatırlamamıza gerek yok, tanımlayıcı değişkenler var
if isValid && isAllowed && isSecure {
...
}
```
## İç içe if döngüleri
```go
if isValid { // 🧠+, tamam, iç içe geçmiş kod yalnızca geçerli girdiye uygulanır
if isSecure { // 🧠++, yalnızca geçerli ve güvenli girdi zaman devam ediyoruz
stuff // 🧠+++
}
}
```
Bilişsel yükü bir de "early return" ile kıyaslayalım:
```go
if !isValid
return
if !isSecure
return
// 🧠, daha önceki getirileri pek umursamıyoruz, eğer buradaysak her şey yolunda demektir
stuff // 🧠+
```
Sadece “her şeyin yolunda gittiği senaryo”ya odaklanırsak, zihnimiz gereksiz ön koşullarla meşgul olmaz, daha rahat çalışırız.
## Inheritance kabusu
Yönetici kullanıcılarımız için birkaç şeyi değiştirmemiz isteniyor: `🧠`
`AdminController extends UserController extends GuestController extends BaseController`
Ohh, işin bir kısmı `BaseController`'da duruyor, bi' kontrol edelim: `🧠+`
Temel rol mekanikleri `GuestController`'da eklenmiş: `🧠++`
`UserController`'da bazı şeyler değiştirilm
gitextract_sp1lepdz/ ├── LICENSE ├── README.es.md ├── README.ja.md ├── README.ko.md ├── README.md ├── README.np.md ├── README.prompt.md ├── README.pt-br.md ├── README.tr.md ├── README.vi.md └── README.zh-cn.md
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (292K chars).
[
{
"path": "LICENSE",
"chars": 18714,
"preview": "Copyright (c) 2023 Artem Zakirullin (https://github.com/zakirullin)\n\nAttribution 4.0 International\n\n===================="
},
{
"path": "README.es.md",
"chars": 41407,
"preview": "# La carga cognitiva es lo que importa\n\n[Prompt](https://github.com/zakirullin/cognitive-load/blob/main/README.prompt.md"
},
{
"path": "README.ja.md",
"chars": 19459,
"preview": "# 認知負荷こそが重要\n\n[読みやすい版](https://minds.md/zakirullin/cognitive) | [中国語翻訳](https://github.com/zakirullin/cognitive-load/blob"
},
{
"path": "README.ko.md",
"chars": 20007,
"preview": "# 인지 부하가 중요합니다\n\n## 소개 (Introduction)\n세상에는 수많은 유행어와 모범 사례가 있지만, 대부분은 실패했습니다. 우리에게는 더 근본적인 것, 틀릴 수 없는 무언가가 필요합니다.\n\n때때로 우리는"
},
{
"path": "README.md",
"chars": 39206,
"preview": "# Cognitive load is what matters\n\n[Prompt](https://github.com/zakirullin/cognitive-load/blob/main/README.prompt.md) | [B"
},
{
"path": "README.np.md",
"chars": 11862,
"preview": "# संज्ञानात्मक भार नै महत्त्वपूर्ण छ\n\n[प्रोम्प्ट](https://github.com/zakirullin/cognitive-load/blob/main/README.prompt.m"
},
{
"path": "README.prompt.md",
"chars": 2883,
"preview": "You are an engineer who writes code for **human brains, not machines**. You favour code that is simple to understand and"
},
{
"path": "README.pt-br.md",
"chars": 41056,
"preview": "# Carga Cognitiva é o que importa\r\n\r\n[Prompt](README.prompt.md) | [Versão do Blog](https://minds.md/zakirullin/cognitive"
},
{
"path": "README.tr.md",
"chars": 36235,
"preview": "# Önemli olan bilişsel yüktür\n\n## Giriş\n\nEtrafta hatrı sayılır sayıda klişe söylemler ve \"best practice\" yapılar var, fa"
},
{
"path": "README.vi.md",
"chars": 36526,
"preview": "# Cognitive load mới là thứ quan trọng\n\n[Prompt](https://github.com/zakirullin/cognitive-load/blob/main/README.prompt.md"
},
{
"path": "README.zh-cn.md",
"chars": 18186,
"preview": "# 认知负荷才是关键\n\n[Prompt](https://github.com/zakirullin/cognitive-load/blob/main/README.prompt.md) | [Blog version](https://m"
}
]
About this extraction
This page contains the full source code of the zakirullin/cognitive-load GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (278.8 KB), approximately 88.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.