[
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2023 Artem Zakirullin (https://github.com/zakirullin)\n\nAttribution 4.0 International\n\n=======================================================================\n\nCreative Commons Corporation (\"Creative Commons\") is not a law firm and\ndoes not provide legal services or legal advice. Distribution of\nCreative Commons public licenses does not create a lawyer-client or\nother relationship. Creative Commons makes its licenses and related\ninformation available on an \"as-is\" basis. Creative Commons gives no\nwarranties regarding its licenses, any material licensed under their\nterms and conditions, or any related information. Creative Commons\ndisclaims all liability for damages resulting from their use to the\nfullest extent possible.\n\nUsing Creative Commons Public Licenses\n\nCreative Commons public licenses provide a standard set of terms and\nconditions that creators and other rights holders may use to share\noriginal works of authorship and other material subject to copyright\nand certain other rights specified in the public license below. The\nfollowing considerations are for informational purposes only, are not\nexhaustive, and do not form part of our licenses.\n\n     Considerations for licensors: Our public licenses are\n     intended for use by those authorized to give the public\n     permission to use material in ways otherwise restricted by\n     copyright and certain other rights. Our licenses are\n     irrevocable. Licensors should read and understand the terms\n     and conditions of the license they choose before applying it.\n     Licensors should also secure all rights necessary before\n     applying our licenses so that the public can reuse the\n     material as expected. Licensors should clearly mark any\n     material not subject to the license. This includes other CC-\n     licensed material, or material used under an exception or\n     limitation to copyright. More considerations for licensors:\n\twiki.creativecommons.org/Considerations_for_licensors\n\n     Considerations for the public: By using one of our public\n     licenses, a licensor grants the public permission to use the\n     licensed material under specified terms and conditions. If\n     the licensor's permission is not necessary for any reason--for\n     example, because of any applicable exception or limitation to\n     copyright--then that use is not regulated by the license. Our\n     licenses grant only permissions under copyright and certain\n     other rights that a licensor has authority to grant. Use of\n     the licensed material may still be restricted for other\n     reasons, including because others have copyright or other\n     rights in the material. A licensor may make special requests,\n     such as asking that all changes be marked or described.\n     Although not required by our licenses, you are encouraged to\n     respect those requests where reasonable. More_considerations\n     for the public:\n\twiki.creativecommons.org/Considerations_for_licensees\n\n=======================================================================\n\nCreative Commons Attribution 4.0 International Public License\n\nBy exercising the Licensed Rights (defined below), You accept and agree\nto be bound by the terms and conditions of this Creative Commons\nAttribution 4.0 International Public License (\"Public License\"). To the\nextent this Public License may be interpreted as a contract, You are\ngranted the Licensed Rights in consideration of Your acceptance of\nthese terms and conditions, and the Licensor grants You such rights in\nconsideration of benefits the Licensor receives from making the\nLicensed Material available under these terms and conditions.\n\n\nSection 1 -- Definitions.\n\n  a. Adapted Material means material subject to Copyright and Similar\n     Rights that is derived from or based upon the Licensed Material\n     and in which the Licensed Material is translated, altered,\n     arranged, transformed, or otherwise modified in a manner requiring\n     permission under the Copyright and Similar Rights held by the\n     Licensor. For purposes of this Public License, where the Licensed\n     Material is a musical work, performance, or sound recording,\n     Adapted Material is always produced where the Licensed Material is\n     synched in timed relation with a moving image.\n\n  b. Adapter's License means the license You apply to Your Copyright\n     and Similar Rights in Your contributions to Adapted Material in\n     accordance with the terms and conditions of this Public License.\n\n  c. Copyright and Similar Rights means copyright and/or similar rights\n     closely related to copyright including, without limitation,\n     performance, broadcast, sound recording, and Sui Generis Database\n     Rights, without regard to how the rights are labeled or\n     categorized. For purposes of this Public License, the rights\n     specified in Section 2(b)(1)-(2) are not Copyright and Similar\n     Rights.\n\n  d. Effective Technological Measures means those measures that, in the\n     absence of proper authority, may not be circumvented under laws\n     fulfilling obligations under Article 11 of the WIPO Copyright\n     Treaty adopted on December 20, 1996, and/or similar international\n     agreements.\n\n  e. Exceptions and Limitations means fair use, fair dealing, and/or\n     any other exception or limitation to Copyright and Similar Rights\n     that applies to Your use of the Licensed Material.\n\n  f. Licensed Material means the artistic or literary work, database,\n     or other material to which the Licensor applied this Public\n     License.\n\n  g. Licensed Rights means the rights granted to You subject to the\n     terms and conditions of this Public License, which are limited to\n     all Copyright and Similar Rights that apply to Your use of the\n     Licensed Material and that the Licensor has authority to license.\n\n  h. Licensor means the individual(s) or entity(ies) granting rights\n     under this Public License.\n\n  i. Share means to provide material to the public by any means or\n     process that requires permission under the Licensed Rights, such\n     as reproduction, public display, public performance, distribution,\n     dissemination, communication, or importation, and to make material\n     available to the public including in ways that members of the\n     public may access the material from a place and at a time\n     individually chosen by them.\n\n  j. Sui Generis Database Rights means rights other than copyright\n     resulting from Directive 96/9/EC of the European Parliament and of\n     the Council of 11 March 1996 on the legal protection of databases,\n     as amended and/or succeeded, as well as other essentially\n     equivalent rights anywhere in the world.\n\n  k. You means the individual or entity exercising the Licensed Rights\n     under this Public License. Your has a corresponding meaning.\n\n\nSection 2 -- Scope.\n\n  a. License grant.\n\n       1. Subject to the terms and conditions of this Public License,\n          the Licensor hereby grants You a worldwide, royalty-free,\n          non-sublicensable, non-exclusive, irrevocable license to\n          exercise the Licensed Rights in the Licensed Material to:\n\n            a. reproduce and Share the Licensed Material, in whole or\n               in part; and\n\n            b. produce, reproduce, and Share Adapted Material.\n\n       2. Exceptions and Limitations. For the avoidance of doubt, where\n          Exceptions and Limitations apply to Your use, this Public\n          License does not apply, and You do not need to comply with\n          its terms and conditions.\n\n       3. Term. The term of this Public License is specified in Section\n          6(a).\n\n       4. Media and formats; technical modifications allowed. The\n          Licensor authorizes You to exercise the Licensed Rights in\n          all media and formats whether now known or hereafter created,\n          and to make technical modifications necessary to do so. The\n          Licensor waives and/or agrees not to assert any right or\n          authority to forbid You from making technical modifications\n          necessary to exercise the Licensed Rights, including\n          technical modifications necessary to circumvent Effective\n          Technological Measures. For purposes of this Public License,\n          simply making modifications authorized by this Section 2(a)\n          (4) never produces Adapted Material.\n\n       5. Downstream recipients.\n\n            a. Offer from the Licensor -- Licensed Material. Every\n               recipient of the Licensed Material automatically\n               receives an offer from the Licensor to exercise the\n               Licensed Rights under the terms and conditions of this\n               Public License.\n\n            b. No downstream restrictions. You may not offer or impose\n               any additional or different terms or conditions on, or\n               apply any Effective Technological Measures to, the\n               Licensed Material if doing so restricts exercise of the\n               Licensed Rights by any recipient of the Licensed\n               Material.\n\n       6. No endorsement. Nothing in this Public License constitutes or\n          may be construed as permission to assert or imply that You\n          are, or that Your use of the Licensed Material is, connected\n          with, or sponsored, endorsed, or granted official status by,\n          the Licensor or others designated to receive attribution as\n          provided in Section 3(a)(1)(A)(i).\n\n  b. Other rights.\n\n       1. Moral rights, such as the right of integrity, are not\n          licensed under this Public License, nor are publicity,\n          privacy, and/or other similar personality rights; however, to\n          the extent possible, the Licensor waives and/or agrees not to\n          assert any such rights held by the Licensor to the limited\n          extent necessary to allow You to exercise the Licensed\n          Rights, but not otherwise.\n\n       2. Patent and trademark rights are not licensed under this\n          Public License.\n\n       3. To the extent possible, the Licensor waives any right to\n          collect royalties from You for the exercise of the Licensed\n          Rights, whether directly or through a collecting society\n          under any voluntary or waivable statutory or compulsory\n          licensing scheme. In all other cases the Licensor expressly\n          reserves any right to collect such royalties.\n\n\nSection 3 -- License Conditions.\n\nYour exercise of the Licensed Rights is expressly made subject to the\nfollowing conditions.\n\n  a. Attribution.\n\n       1. If You Share the Licensed Material (including in modified\n          form), You must:\n\n            a. retain the following if it is supplied by the Licensor\n               with the Licensed Material:\n\n                 i. identification of the creator(s) of the Licensed\n                    Material and any others designated to receive\n                    attribution, in any reasonable manner requested by\n                    the Licensor (including by pseudonym if\n                    designated);\n\n                ii. a copyright notice;\n\n               iii. a notice that refers to this Public License;\n\n                iv. a notice that refers to the disclaimer of\n                    warranties;\n\n                 v. a URI or hyperlink to the Licensed Material to the\n                    extent reasonably practicable;\n\n            b. indicate if You modified the Licensed Material and\n               retain an indication of any previous modifications; and\n\n            c. indicate the Licensed Material is licensed under this\n               Public License, and include the text of, or the URI or\n               hyperlink to, this Public License.\n\n       2. You may satisfy the conditions in Section 3(a)(1) in any\n          reasonable manner based on the medium, means, and context in\n          which You Share the Licensed Material. For example, it may be\n          reasonable to satisfy the conditions by providing a URI or\n          hyperlink to a resource that includes the required\n          information.\n\n       3. If requested by the Licensor, You must remove any of the\n          information required by Section 3(a)(1)(A) to the extent\n          reasonably practicable.\n\n       4. If You Share Adapted Material You produce, the Adapter's\n          License You apply must not prevent recipients of the Adapted\n          Material from complying with this Public License.\n\n\nSection 4 -- Sui Generis Database Rights.\n\nWhere the Licensed Rights include Sui Generis Database Rights that\napply to Your use of the Licensed Material:\n\n  a. for the avoidance of doubt, Section 2(a)(1) grants You the right\n     to extract, reuse, reproduce, and Share all or a substantial\n     portion of the contents of the database;\n\n  b. if You include all or a substantial portion of the database\n     contents in a database in which You have Sui Generis Database\n     Rights, then the database in which You have Sui Generis Database\n     Rights (but not its individual contents) is Adapted Material; and\n\n  c. You must comply with the conditions in Section 3(a) if You Share\n     all or a substantial portion of the contents of the database.\n\nFor the avoidance of doubt, this Section 4 supplements and does not\nreplace Your obligations under this Public License where the Licensed\nRights include other Copyright and Similar Rights.\n\n\nSection 5 -- Disclaimer of Warranties and Limitation of Liability.\n\n  a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE\n     EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS\n     AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF\n     ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,\n     IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,\n     WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR\n     PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,\n     ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT\n     KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT\n     ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.\n\n  b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE\n     TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,\n     NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,\n     INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,\n     COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR\n     USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN\n     ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR\n     DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR\n     IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.\n\n  c. The disclaimer of warranties and limitation of liability provided\n     above shall be interpreted in a manner that, to the extent\n     possible, most closely approximates an absolute disclaimer and\n     waiver of all liability.\n\n\nSection 6 -- Term and Termination.\n\n  a. This Public License applies for the term of the Copyright and\n     Similar Rights licensed here. However, if You fail to comply with\n     this Public License, then Your rights under this Public License\n     terminate automatically.\n\n  b. Where Your right to use the Licensed Material has terminated under\n     Section 6(a), it reinstates:\n\n       1. automatically as of the date the violation is cured, provided\n          it is cured within 30 days of Your discovery of the\n          violation; or\n\n       2. upon express reinstatement by the Licensor.\n\n     For the avoidance of doubt, this Section 6(b) does not affect any\n     right the Licensor may have to seek remedies for Your violations\n     of this Public License.\n\n  c. For the avoidance of doubt, the Licensor may also offer the\n     Licensed Material under separate terms or conditions or stop\n     distributing the Licensed Material at any time; however, doing so\n     will not terminate this Public License.\n\n  d. Sections 1, 5, 6, 7, and 8 survive termination of this Public\n     License.\n\n\nSection 7 -- Other Terms and Conditions.\n\n  a. The Licensor shall not be bound by any additional or different\n     terms or conditions communicated by You unless expressly agreed.\n\n  b. Any arrangements, understandings, or agreements regarding the\n     Licensed Material not stated herein are separate from and\n     independent of the terms and conditions of this Public License.\n\n\nSection 8 -- Interpretation.\n\n  a. For the avoidance of doubt, this Public License does not, and\n     shall not be interpreted to, reduce, limit, restrict, or impose\n     conditions on any use of the Licensed Material that could lawfully\n     be made without permission under this Public License.\n\n  b. To the extent possible, if any provision of this Public License is\n     deemed unenforceable, it shall be automatically reformed to the\n     minimum extent necessary to make it enforceable. If the provision\n     cannot be reformed, it shall be severed from this Public License\n     without affecting the enforceability of the remaining terms and\n     conditions.\n\n  c. No term or condition of this Public License will be waived and no\n     failure to comply consented to unless expressly agreed to by the\n     Licensor.\n\n  d. Nothing in this Public License constitutes or may be interpreted\n     as a limitation upon, or waiver of, any privileges and immunities\n     that apply to the Licensor or You, including from the legal\n     processes of any jurisdiction or authority.\n\n\n=======================================================================\n\nCreative Commons is not a party to its public\nlicenses. Notwithstanding, Creative Commons may elect to apply one of\nits public licenses to material it publishes and in those instances\nwill be considered the “Licensor.” The text of the Creative Commons\npublic licenses is dedicated to the public domain under the CC0 Public\nDomain Dedication. Except for the limited purpose of indicating that\nmaterial is shared under a Creative Commons public license or as\notherwise permitted by the Creative Commons policies published at\ncreativecommons.org/policies, Creative Commons does not authorize the\nuse of the trademark \"Creative Commons\" or any other trademark or logo\nof Creative Commons without its prior written consent including,\nwithout limitation, in connection with any unauthorized modifications\nto any of its public licenses or any other arrangements,\nunderstandings, or agreements concerning use of licensed material. For\nthe avoidance of doubt, this paragraph does not form part of the\npublic licenses.\n\nCreative Commons may be contacted at creativecommons.org."
  },
  {
    "path": "README.es.md",
    "content": "# La carga cognitiva es lo que importa\n\n[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)\n\n*Este es un documento \"vivo\", última actualización: octubre de 2025. ¡Tus contribuciones son bienvenidas!*\n\n## Introducción\nExisten 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.\n\nA 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.  \n\nComo 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.\n\n## Carga cognitiva\n> La carga cognitiva es cuánto necesita pensar un desarrollador para completar una tarea.\n\nAl 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.\n\n*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.***\n\n<div align=\"center\">\n  <img src=\"/img/cognitiveloadv6.png\" alt=\"Cognitive load\" width=\"750\">\n</div>\n\nDeberíamos reducir la carga cognitiva en nuestro proyectos tanto como sea posible.\n\n<details>\n  <summary><b>Carga cognitiva e interrupciones</b></summary>\n  <div align=\"center\">\n    <img src=\"img/interruption.jpeg\" width=\"480\">\n  </div>\n</details>\n\n> 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.\n\n## Tipos de carga cognitiva\n**Intrínseca**: causada por la dificultad inherente de una tarea. No se puede reducir; es la esencia misma del desarrollo de software.\n\n**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.\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Intrínsica vs Extraña\" width=\"600\">\n</div>\n\nPasemos directamente a los ejemplos prácticos concretos de carga cognitiva extraña.\n\n---\n\nNos referiremos al nivel de carga cognitiva de la siguiente manera:\n`🧠`: memoria de trabajo fresca, carga cognitiva cero\n`🧠++`: dos datos en nuestra memoria de trabajo, carga cognitiva aumentada\n`🤯`: sobrecarga cognitiva, más de 4 datos\n\n> Nuestro cerebro es mucho más complejo e inexplorado, pero podemos seguir con este modelo simplista.\n\n## Condicionales complejas\n```go\nif val > algunaConstante // 🧠+\n    && (condicion2 || condicion3) // 🧠+++, la condición anterior debe ser verdadera, una de c2 o c3 debe ser verdadera\n    && (condicion4 && !condicion5) { // 🤯, estamos jodidos en este punto\n    ...\n}\n```\n\nIntroduzca variables intermedias con nombres significativos:\n```go\nesValido = val > algunaConstante\nesPermitido = condicion2 || condicion3\nesSeguro = condicion4 && !condicion5 \n// 🧠, no necesitamos recordar las condiciones, hay variables descriptivas\nif esValido && isPermitido && esSeguro {\n    ...\n}\n```\n\n## ifs anidados\n```go\nif esValido { // 🧠+, okay, el código anidado se aplica solo a entradas válidas\n    if esSeguro { // 🧠++, hacemos cosas solo para información válida y segura\n        cosas // 🧠+++\n    }\n} \n```\n\nCompárelo con los resultados de antes:\n```go\nif !esValido\n    return\n \nif !esSeguro\n    return\n\n// 🧠, realmente no nos importan los retornos tempranos, si estamos aquí entonces todo bien\n\ncosas // 🧠+\n```\n\nPodemos centrarnos únicamente en el camino feliz, liberando así nuestra memoria de trabajo de todo tipo de precondiciones.\n\n## La pesadilla de la herencia\nSe nos pide que cambiemos algunas cosas para nuestros usuarios administradores: `🧠`\n\n`AdminController extends UserController extends GuestController extends BaseController`\n\nOhh, parte de la funcionalidad está en `BaseController`, echemos un vistazo: `🧠+`\nSe introdujeron las mecánicas de roles básicos en `GuestController`: `🧠++`\nLas cosas se modificaron parcialmente en `UserController`: `🧠+++`\nPor fin estamos aquí, `AdminController`, ¡vamos a programar cosas! `🧠++++`\n\nAh, espera, existe `SuperuserController`, que extiende `AdminController`. Al modificar `AdminController`, podemos romper cosas en la clase heredada, así que analicemos primero `SuperuserController`: `🤯`\n\nPrefiere la composición a la herencia. No entraremos en detalles - hay [mucho material](https://www.youtube.com/watch?v=hxGOiiR9ZKg) disponible al respecto.\n\n## Demasiados métodos, clases o módulos pequeños\n> Método, clase y módulo son intercambiables en este contexto.\n\nMantras 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.\n\n**Módulo profundo**: interfaz simple, funcionalidad compleja.\n**Módulo superficial**: interfaz relativamente compleja en comparación con la pequeña funcionalidad que ofrece. \n\n<div align=\"center\">\n  <img src=\"/img/deepmodulev8.png\" alt=\"Módulo profundo\" width=\"700\">\n</div>\n\nTener 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. \n\n> Ocultar información es de suma importancia y no ocultamos tanta complejidad en módulos superficiales.\n\nTengo 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.\n\nAl 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.\n \n\n> Los mejores componentes son aquellos que proporcionan una funcionalidad potente pero tienen una interfaz sencilla.  \n> \n> *John Ousterhout, Una filosofía del diseño de software*\n\nLa interfaz de I/O de Unix es muy sencilla. Solo tiene cinco llamadas básicas:\n```python\nopen(path, flags, permissions)\nread(fd, buffer, count)\nwrite(fd, buffer, count)\nlseek(fd, offset, referencePosition)\nclose(fd)\n```\n\nUna 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.\n\n> 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).\n\n<details>\n    <summary><b>Las cosas importantes deben ser grandes, ejemplos</b></summary>\n    <br>\n    <div align=\"center\">\n        <img src=\"/img/dirty.png\" alt=\"Limpio vs Sucio\" width=\"600\">\n    </div>\n    <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>\n    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.\n</details>\n\nP.D. Si crees que apoyamos objetos divinos inflados y con demasiadas responsabilidades, te equivocas.\n\n## Responsable de una cosa\nCon 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.  \n\nRealizamos cambios en nuestros sistemas para satisfacer a nuestros usuarios y actores. Somos responsables ante ellos.\n\n> Un módulo debe ser responsable ante un solo usuario o actor.\n\nDe 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.    \n\nPero 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.  \n\n## Demasiados microservicios superficiales\nEste 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.\n\nEn 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. `🤯`\n\n¿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.*\n\nEl [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.\n\nUn 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.\n\n## Lenguajes ricos en funciones\nNos emocionamos cuando se lanzan nuevas funciones en nuestro lenguaje favorito. Dedicamos tiempo a aprenderlas y desarrollamos código a partir de ellas.\n\nSi 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!**\n \n**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.** `🤯`\n\nEstas declaraciones las hace nada menos que Rob Pike.\n\n> Reduce la carga cognitiva limitando el número de opciones. \n\nLas características del lenguaje están bien, siempre que sean ortogonales entre sí.\n\n<details>\n  <summary><b>Reflexiones de un ingeniero con 20 años de experiencia en C++ ⭐️</b></summary>\n  <br>\n  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>\n  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>\n  Como podrás imaginar, el token <code>||</code> tiene un significado diferente en <code>requires ((!P&lt;T&gt; || !Q&lt;T&gt;))</code> y en <code>requires (!(P&lt;T&gt; || Q&lt;T&gt;))</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>\n  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>\n  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>\n  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>\n  <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>\n  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>\n  <b>De ninguna manera estoy tratando de culpar a C++.</b> Me encanta el lenguaje. Es solo que ahora estoy cansado.<br><br>\n  <p>Gracias a <a href=\"https://0xd34df00d.me\" target=\"_blank\">0xd34df00d</a> por el escrito.</p>\n</details>\n\n\n## Lógica de negocio y códigos de estado HTTP\nEn el backend, devolvemos: \n`401` para token JWT vencido\n`403` para acceso insuficiente\n`418` para usuarios bloqueados \n\nLos 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:\n`401` es para token JWT vencido // `🧠+`, ok solo recuérdalo temporalmente \n`403` es para acceso insuficeinte // `🧠++`  \n`418` es para usuarios bloqueados // `🧠+++`  \n\nLos 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.\n\nEntonces entran en juego los ingenieros de control de calidad:\n\"Oye, tengo el estado `403`, ¿ese token está vencido o no hay suficiente acceso?\"\n**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.**\n\n¿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:\n```json\n{\n    \"code\": \"jwt_ha_expirado\"\n}\n```\n\nCarga cognitiva en el frontend: `🧠` (genial, no se tienen en cuenta los hechos)\nCarga cognitiva en el control de calidad: `🧠`\n\nLa 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.\n\n> 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.\n\nP.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.\n\n## Abusar del principio DRY\n\nNo 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.\n\nHoy 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.`🤯`  \n\nDe 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.\n\nRob Pike una vez dijo:\n\n> Un poco de copia es mejor que un poco de dependencia.\n\nNos 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.\n\n**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.\n\n## Acoplamiento estrecho con un marco\nHay 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.\n\nPeor 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.`🤯`\n\n**¡De ninguna manera abogamos por inventar todo desde cero!**\n\nPodemos 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. \n\n> [Porqué odio los Frameworks](https://minds.md/benji/frameworks)\n\n## Arquitectura en capas\nHay un cierto entusiasmo ingenieril en torno a todo esto.\n\nYo 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. `🤯`\n\n**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. `🤯`  \n\nEsta 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.**\n\n<details>\n    <summary><b>Principios y experiencia de programación</b></summary>\n    <div align=\"center\">\n        <img src=\"img/complexity.png\" alt=\"Código super simple\" width=\"500\">\n    </div>\n    <a href=\"https://twitter.com/flaviocopes\">@flaviocopes</a>\n</details>\n\nSi 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).  \n\n> Con un número suficiente de usuarios de una API, \n> no importa lo que prometas en el contrato:\n> alguien dependerá de todos \n> los comportamientos observables de tu sistema.\n\nRealizamos 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.\n\n**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.\n\nEstas 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).\n\nNo 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.\n\n**[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.**\n\n<div align=\"center\">\n  <img src=\"/img/layers.png\" alt=\"Layers\" width=\"400\">\n</div>\n\n## Diseño orientado al dominio\nEl 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.\n\nEl 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.\n\nEs 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. `🤯`  \n\nLas 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.\n\n## Carga cognitiva en proyectos familiares\n\n> 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!\n>\n> 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.\n>\n> 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!\".\n>\n> 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.  \n>\n> *Gracias a [Dan North](https://dannorth.net) por su comentario*.  \n\nSi has interiorizado los modelos mentales del proyecto en tu memoria a largo plazo, no experimentarás una alta carga cognitiva. \n\n<div align=\"center\">\n  <img src=\"/img/mentalmodelsv15.png\" alt=\"Modelos mentales\" width=\"700\">\n</div>\n\nCuantos más modelos mentales tenga que aprender un nuevo desarrollador, más tiempo tardará en aportar valor.\n\nUna 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.\n\nSi 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.\n\n## Ejemplos\n- Nuestra arquitectura es una arquitectura estándar de aplicación CRUD, [un monolito de Python sobre Postgres](https://danluu.com/simple-architectures/)\n- Cómo Instagram escaló a 14 millones de usuarios con [solo 3 ingenieros](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)\n- 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\n- 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)\n- Diseño para la comprensibilidad: [El algoritmo de consenso Raft](https://www.youtube.com/watch?v=vYp4LYbnnW8)\n\nEstas arquitecturas son un tanto aburridas y fáciles de entender. Cualquiera puede comprenderlas sin mucho esfuerzo mental.\n\nInvolucra a los desarrolladores junior en las revisiones de arquitectura; te ayudarán a identificar las áreas que requieren mayor esfuerzo mental.\n\n> 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. \n> \n> *Fred Brooks, El mítico hombre-mes*\n\n**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.\n\n> 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.\n>\n> *Brian Kernighan*\n\nEn 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:\n- ¿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?\n- ¿Podemos realizar cambios rápidamente, o hay muchas incógnitas y la gente tiene miedo de tocar cosas?\n- ¿Pueden los nuevos usuarios añadir funcionalidades rápidamente? ¿Hay modelos mentales específicos que aprender?\n\n> ¿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.**\n\nEstas 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.\n\n## Conclusión\nImaginemos 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. `🤯`  \n\n¿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.**\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Autor inteligente\" width=\"600\">\n</div>\n\nDebemos reducir cualquier carga cognitiva que exceda lo que es intrínseco al trabajo que realizamos.\n\n---\n[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\n\n<details>\n    <summary><b>Comentarios</b></summary>\n    <br>\n    <p><strong>Rob Pike</strong> <i>(Unix, Golang)</i><br>Nice article.</p>\n    <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>\n    <p><strong><a href=\"https://x.com/elonmusk/status/1872346903792566655\" target=\"_blank\">Elon Musk</a></strong> <i>(Rockets)</i><br>True.</p>\n    <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>\n    <p>The irony? Many of these complexity-inducing patterns were implemented in the name of \"clean code.\"</p>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <p>Sometimes the simplest solution is the best one, even in a complex system.</p>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n</details>\n"
  },
  {
    "path": "README.ja.md",
    "content": "# 認知負荷こそが重要\n\n[読みやすい版](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)\n\n*これは生きた文書であり、最終更新は**2025年8月**です。あなたの貢献を歓迎します！*\n\n## はじめに\n世の中には多くのバズワードやベストプラクティスがありますが、そのほとんどがうまくいっていません。私たちにはもっと根本的な、誤りようのないものが必要です。\n\nコードを読んでいて混乱を感じることがあります。混乱は時間とお金を消費します。混乱の原因は高い*認知負荷*にあります。これは決して抽象論ではなく、**人間の根本的制約**なのです。私たちはそれを実感できます。\n\nコードを書くよりもコードを読んで理解することに費やす時間のほうがはるかに長いため、私たちは常に、過度の認知負荷をコードに埋め込んでいないかを自問すべきです。\n\n## 認知負荷\n> 認知負荷とは、開発者がタスクを完了するために考える必要がある量のことです。\n\nコードを読むとき、変数の値、制御フローロジック、呼び出しシーケンスなどを頭に保持します。平均的な人はワーキングメモリに約[4つのチャンク](https://github.com/zakirullin/cognitive-load/issues/16)を保持できます。認知負荷がこの閾値を超えると、理解が一気に難しくなります。\n\n> 完全に馴染みのないプロジェクトにバグ修正を依頼されたとしましょう。とても賢い開発者が貢献していたと聞かされました。多くの洗練されたアーキテクチャ、おしゃれなライブラリ、最新のテクノロジーが使われていました。つまり、**著者は私たちに高い認知負荷を作り出していた**のです。\n\n<div align=\"center\">\n  <img src=\"/img/cognitiveloadv6.png\" alt=\"認知負荷\" width=\"750\">\n</div>\n\n私たちはプロジェクトの認知負荷を可能な限り減らすべきです。\n\n<details>\n  <summary><b>認知負荷と中断</b></summary>\n  <img src=\"img/interruption.jpeg\"><br>\n</details>\n\n## 認知負荷の種類\n**内在的** - タスクの本来の難易度によって引き起こされます。これは削減できず、ソフトウェア開発の核心にあるものです。\n\n**外在的** - 情報の提示方法によって作り出されます。タスクに直接関係しない要因、例えば賢い開発者のクセのようなもので引き起こされます。大幅に削減可能です。私たちはこの種の認知負荷に焦点を当てます。\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"内在的 vs 外在的\" width=\"600\">\n</div>\n\n外在的認知負荷の具体的で実践的な例にすぐに飛び込みましょう。\n\n---\n\n認知負荷のレベルを以下のように表記します：\n`🧠`: 新鮮なワーキングメモリ、認知負荷ゼロ\n`🧠++`: ワーキングメモリに2つの事実、認知負荷増加\n`🤯`: 認知過負荷、4つ以上の事実\n\n> 私たちの脳ははるかに複雑で未探索ですが、この単純化されたモデルで進めることができます。\n\n## 複雑な条件文\n```go\nif val > someConstant // 🧠+\n    && (condition2 || condition3) // 🧠+++、前の条件が真で、c2かc3のいずれかが真である必要性\n    && (condition4 && !condition5) { // 🤯、この時点で理解が破綻しがち\n    ...\n}\n```\n\n意味のある名前を持つ中間変数を導入します：\n```go\nisValid = val > someConstant\nisAllowed = condition2 || condition3\nisSecure = condition4 && !condition5 \n// 🧠、記述的な変数があるので、条件を覚える必要がありません\nif isValid && isAllowed && isSecure {\n    ...\n}\n```\n\n## ネストしたif文\n```go\nif isValid { // 🧠+、ネストしたコードは有効な入力にのみ適用\n    if isSecure { // 🧠++、有効で安全な入力にのみ処理\n        stuff // 🧠+++\n    }\n} \n```\n\n早期リターンと比較してみましょう：\n```go\nif !isValid\n    return\n \nif !isSecure\n    return\n\n// 🧠、以前のリターンは気にしません。ここに到達したということは、すべてがOK\n\nstuff // 🧠+\n```\n\nハッピーパス（正常系）だけに集中でき、ワーキングメモリをあらゆる前提条件から解放します。\n\n## 継承の悪夢\n管理者ユーザーのためにいくつかの変更を依頼されました：`🧠`\n\n`AdminController extends UserController extends GuestController extends BaseController`\n\nああ、機能の一部は`BaseController`にあります、見てみましょう：`🧠+`\n基本的な役割メカニクスが`GuestController`で導入されました：`🧠++`\n`UserController`で部分的に変更されました：`🧠+++`\nついにここに来ました、`AdminController`、コーディングしましょう！`🧠++++`\n\nああ、待って、`AdminController`を継承する`SuperuserController`があります。`AdminController`を変更することで継承クラスで問題が起こる可能性があるので、まず`SuperuserController`を調べましょう：`🤯`\n\n継承よりコンポジション（委譲）を好みましょう。詳細は割愛します。 - 参考資料は[こちら](https://www.youtube.com/watch?v=hxGOiiR9ZKg)が充実しています。\n\n## 小さなメソッド、クラス、モジュールが多すぎる\n> この文脈では、メソッド、クラス、モジュールは交換可能です\n\n「メソッドは15行未満であるべき」や「クラスは小さくあるべき」といった格言は、やや間違っていることが判明しました。\n\n**深いモジュール** - シンプルなインターフェース、複雑な機能\n**浅いモジュール** - インターフェースが提供する小さな機能に対して相対的に複雑\n\n<div align=\"center\">\n  <img src=\"/img/deepmodulev8.png\" alt=\"深いモジュール\" width=\"700\">\n</div>\n\n浅いモジュールが多すぎると、プロジェクトを理解するのが困難になる可能性があります。**各モジュールの責任を頭に置くだけでなく、それらすべてのインタラクションも把握する必要があります**。浅いモジュールの目的を理解するために、まずすべての関連モジュールの機能を調べる必要があります。そのような浅いコンポーネント間をジャンプするのは精神的に疲れます。人間は<a target=\"_blank\" href=\"https://blog.separateconcerns.com/2023-09-11-linear-code.html\">線形に読む</a>のが自然です。\n\n> 情報隠蔽は最重要であり、浅いモジュールでは複雑性をそれほど隠せません。\n\n私には2つのペットプロジェクトがあり、どちらも約5,000行のコードです。最初のプロジェクトには80の浅いクラスがあり、2番目のプロジェクトには7つの深いクラスしかありませんでした。これらのプロジェクトのメンテナンスを1年半していませんでした。\n\n戻ってきたとき、最初のプロジェクトの80のクラス間のすべてのインタラクションを解きほぐすのが非常に困難であることに気づきました。コーディングを始める前に、膨大な認知負荷を再構築する必要がありました。一方、2番目のプロジェクトは、シンプルなインターフェースを持つ少数の深いクラスしかなかったため、すぐに把握できました。\n\n> 最高のコンポーネントは、強力な機能を提供しながらシンプルなインターフェースを持つものです。\n> **John K. Ousterhout**\n\nUNIX I/Oのインターフェースは非常にシンプルです。基本的な呼び出しは5つだけです：\n```python\nopen(path, flags, permissions)\nread(fd, buffer, count)\nwrite(fd, buffer, count)\nlseek(fd, offset, referencePosition)\nclose(fd)\n```\n\nこのインターフェースの現代的な実装は**数十万行のコード**を持ちます。多くの複雑性が内部に隠蔽されています。しかし、シンプルなインターフェースのおかげで使いやすいのです。\n\n> この深いモジュールの例は、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)。\n\nP.S. 責任が多すぎる肥大化したGodオブジェクト（神オブジェクト）を支持していると思われるなら、それは間違いです。\n\n## 一つのことに責任を持つ\nあまりにもしばしば、曖昧な「モジュールは一つの、そして唯一の一つのことに責任を持つべき」原則に従って、多くの浅いモジュールを作ることになります。このぼんやりとした「一つのこと」とは何でしょうか？オブジェクトをインスタンス化することは一つのことでしょうか？だから[MetricsProviderFactoryFactory](https://minds.md/benji/frameworks)は問題ないように見えます。**そのようなクラスの名前とインターフェースは、実装全体よりも精神的に負荷が大きくなる傾向があります。それはどのような抽象化でしょうか？** 何かが間違っています。\n\n私たちはユーザーや利害関係者を満足させるためにシステムを変更します。私たちは彼らに対して責任があります。\n\n> モジュールは一つの、そして唯一の一つのユーザーまたは利害関係者に責任を持つべきです。\n\nこれが単一責任原則の本質です。簡単に言えば、一箇所にバグを導入して、2人の異なるビジネス関係者が苦情を言いに来た場合、原則に違反しています。モジュールで行うことの数とは関係ありません。\n\nしかし今でも、この規則は害を与える可能性があります。この原則は個人の数だけ異なる方法で理解される可能性があります。より良いアプローチは、それがどのくらいの認知負荷を作り出すかを見ることです。一箇所での変更が異なるビジネス領域間で連鎖反応を引き起こす可能性があることを覚えておくのは、精神的に負荷が大きいです。それだけのことで、学ぶべき難しい用語はありません。\n\n## 浅いマイクロサービスが多すぎる\nこの浅い-深いモジュール原則はスケールに依存せず、マイクロサービスアーキテクチャにも適用できます。浅いマイクロサービスが多すぎるのは良くありません - 業界は多少「マクロサービス」、つまりそれほど浅くない（=深い）サービスに向かっています。最悪で修正が困難な現象の一つは、いわゆる分散モノリスで、これはしばしばこの過度に粒度の細かい浅い分離の結果です。\n\nかつて、5人の開発者チームが17個（！）のマイクロサービスを導入したスタートアップをコンサルティングしました。彼らは10ヶ月スケジュールが遅れていて、パブリックリリースには程遠い状態でした。新しい要件のたびに4つ以上のマイクロサービスでの変更が必要でした。そのような分散システムで問題を再現しデバッグするには膨大な時間がかかりました。市場投入時間と認知負荷の両方が許容できないほど高かったのです。`🤯`\n\nこれは新しいシステムの不確実性に対する正しいアプローチでしょうか？最初に正しい論理的境界を引き出すことは非常に困難です。重要なのは、情報が最も多い時点まで責任を持って待てるだけ決定を遅らせることです。最初にネットワーク層を導入することで、最初から設計決定を元に戻すのを困難にしています。チームの唯一の正当化は：「FAANG企業がマイクロサービスアーキテクチャが効果的であることを証明した」でした。**現実に立ち返りましょう。**\n\n[Tanenbaum-Torvalds討論](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate)では、Linuxのモノリシック設計が欠陥があり時代遅れであり、代わりにマイクロカーネルアーキテクチャを使うべきだと主張されました。確かに、マイクロカーネル設計は「理論的で美的な」観点から優れているように見えました。実用的な側面では――30年経った今、マイクロカーネルベースのGNU Hurdはまだ開発中で、モノリシックなLinuxが至る所で使われています。このページはLinuxで動いており、あなたのスマートティーポットもLinuxで動いています。モノリシックなLinuxで。\n\n本当に分離されたモジュールを持つ良く作られたモノリスは、多くのマイクロサービスよりもはるかに柔軟であることが多いです。また、維持するのに必要な認知的努力もはるかに少なくなります。開発チームのスケーリングなど、別々のデプロイメントの必要性が重要になった場合にのみ、モジュール間、将来のマイクロサービス間にネットワーク層を追加することを検討すべきです。\n\n## 機能豊富な言語\nお気に入りの言語で新機能がリリースされるとワクワクします。これらの機能を学ぶのに時間を費やし、その上にコードを構築します。\n\n多くの機能がある場合、数行のコードを書くのに30分を費やして、ある機能を使うか別の機能を使うかを考えるかもしれません。それは時間の無駄です。しかし、もっと悪いことに、**後で戻ってきたときに、その思考プロセスを再現する必要があります！**\n\n**この複雑なプログラムを理解するだけでなく、利用可能な機能から問題にアプローチするこの方法をプログラマーがなぜ決定したのかを理解する必要があります。** `🤯`\n\nこれらの発言は他ならぬRob Pikeによるものです。\n\n> 選択肢の数を制限して認知負荷を減らす。\n\n言語機能は、お互いに直交している限り問題ありません。\n\n<details>\n  <summary><b>20年のC++経験を持つエンジニアからの考え ⭐️</b></summary>\n  <br>\n  先日RSSリーダーを見ていると、「C++」タグの下に未読記事が約300個あることに気づきました。昨年の夏以降、この言語についての記事を一つも読んでおらず、気分は良好です！<br><br>\n  私は20年間C++を使っており、それは人生の約3分の2です。私の経験の大部分は、言語の最も暗い部分（あらゆる種類の未定義動作など）を扱うことにあります。それは再利用可能な経験ではなく、今それをすべて捨てるのはちょっと不気味です。<br><br>\n  例えば、トークン<code>||</code>が<code>requires ((!P&lt;T&gt; || !Q&lt;T&gt;))</code>と<code>requires (!(P&lt;T&gt; || Q&lt;T&gt;))</code>で異なる意味を持つことを想像できますか。最初は制約分離、2番目は昔ながらの論理OR演算子で、これらは異なる動作をします。<br><br>\n  自明な型のためのスペースを割り当て、そこに一連のバイトを<code>memcpy</code>するだけでは、追加の努力なしにはオブジェクトの生存期間を開始できません。これはC++20以前の場合でした。C++20で修正されましたが、言語の認知負荷は増加しただけです。<br><br>\n  物事が修正されても認知負荷は常に増加しています。私はプロとして、何が修正されたか、いつ修正されたか、以前はどうだったかを知っている必要があります。確かに、C++はレガシーサポートが得意で、それはあなたが<b>そのレガシーに直面する</b>ことも意味します。例えば、先月同僚がC++03でのある動作について尋ねました。<code>🤯</code><br><br>\n  初期化の方法は20通りありました。統一初期化構文が追加されました。今は21通りの初期化方法があります。ところで、初期化リストからコンストラクタを選択する規則を覚えていますか？情報の損失が最小の暗黙変換についてのもので、<i>しかし値が静的に分かっている場合は</i>、それから... <code>🤯</code><br><br>\n  <b>この増加した認知負荷は、手元のビジネスタスクによるものではありません。それはドメインの本質的な複雑性ではありません。歴史的な理由で存在しているだけです</b>（<i>外在的認知負荷</i>）。<br><br>\n  私はいくつかのルールを決める必要がありました。例えば、そのコード行が明らかでなく、標準を覚えなければならない場合は、そのように書かないほうが良いです。ちなみに、標準は約1500ページの長さです。<br><br>\n  <b>決してC++を非難しようとしているのではありません。</b>私はこの言語を愛しています。ただ、今は疲れているのです。<br><br>\n  <p><a href=\"https://0xd34df00d.me\" target=\"_blank\">0xd34df00d</a>に執筆していただき、ありがとうございます。</p>\n</details>\n\n## ビジネスロジックとHTTPステータスコード\nバックエンドで以下を返します：\n`401` 期限切れJWTトークンの場合\n`403` アクセス権限不足の場合\n`418` 利用停止中のユーザーの場合\n\nフロントエンドのエンジニアは、バックエンドAPIを使用してログイン機能を実装します。彼らは一時的に以下の認知負荷を頭の中に保持しなければなりません：\n`401`は期限切れJWTトークンの場合 // `🧠+`、一時的に覚えておく\n`403`はアクセス権限不足の場合 // `🧠++`\n`418`は利用停止中のユーザーの場合 // `🧠+++`\n\nフロントエンド開発者は（願わくば）彼らの側で何らかの`数値ステータス → 意味`辞書を導入し、後続の貢献者世代がこのマッピングを頭の中で再構築する必要がないようにするでしょう。\n\nそれからQAエンジニアが登場します：\n「やあ、`403`ステータスを受け取ったけど、これは期限切れトークンなの、それともアクセス権限不足なの？」\n**QAエンジニアは、バックエンドのエンジニアがかつて作成した認知負荷を再構築しなければならないので、すぐにテストに飛び込むことができません。**\n\nなぜこのカスタムマッピングをワーキングメモリに保持するのでしょうか？ビジネス詳細をHTTP転送プロトコルから抽象化し、レスポンスボディに自己記述的なコードを直接返すほうが良いです：\n```json\n{\n    \"code\": \"jwt_has_expired\"\n}\n```\n\nフロントエンド側の認知負荷：`🧠`（新鮮、頭に保持される事実なし）\nQA側の認知負荷：`🧠`\n\n同じ規則があらゆる数値ステータス（データベースやその他の場所）に適用されます - **自己記述的な文字列を好みましょう**。メモリを最適化するための640Kコンピューターの時代ではありません。\n\n> 人々は`401`と`403`の間で議論し、自分の心的モデルに基づいて決定を下すのに時間を費やします。新しい開発者が入ってきて、その思考プロセスを再構築する必要があります。コードの「なぜ」（ADR）を文書化して、新参者が下された決定を理解できるようにしているかもしれません。しかし結局のところ、それは全く意味をなしません。エラーをユーザー関連またはサーバー関連に分離することはできますが、それ以外は物事がぼやけています。\n\nP.S. 「認証」と「認可」を区別するのはしばしば精神的に負荷が大きいです。認知負荷を減らすために、[「ログイン」と「権限」](https://ntietz.com/blog/lets-say-instead-of-auth/)のようなより簡単な用語を使うことができます。\n\n## DRY原則の乱用\n\nDon't repeat yourself（自分を繰り返すな） - これはソフトウェアエンジニアとして教わる最初の原則の一つです。それは私たち自身に深く埋め込まれており、数行の余分なコードの存在に耐えられません。一般的には良い基本的な規則ですが、使いすぎると私たちが処理できない認知負荷につながります。\n\n今日では、誰もが論理的に分離されたコンポーネントに基づいてソフトウェアを構築しています。多くの場合、これらは別々のサービスを表す複数のコードベースに分散されています。あらゆる繰り返しを排除しようと努力すると、関連のないコンポーネント間で密結合を作り出すことになるかもしれません。結果として、一部の変更は他の一見関係のない領域で予期しない結果をもたらす可能性があります。また、システム全体に影響を与えることなく個々のコンポーネントを置き換えたり変更したりする能力を妨げることもあります。`🤯`\n\n実際、同じ問題は単一のモジュール内でも発生します。長期的に実際には存在しないかもしれない認識された類似性に基づいて、共通機能を早すぎる段階で抽出するかもしれません。これは変更や拡張が困難な不要な抽象化をもたらす可能性があります。\n\nRob Pikeはかつて言いました：\n\n> 少しの依存よりも少しのコピーのほうが良い。\n\n車輪を再発明しないことに強く惑わされ、私たち自身で簡単に書けるような小さな関数を使うために大きくて重いライブラリをインポートしがちです。\n\n**すべての依存関係は実質的に「あなたのコード」です。** インポートされたライブラリの10以上のレベルのスタックトレースを通して何が悪かったかを把握する（*物事は悪化するから*）のは苦痛です。\n\n## フレームワークとの密結合\nフレームワークには多くの「魔法」があります。フレームワークに過度に依存することで、**すべての今後の開発者にその「魔法」を最初に学ばせることを強制します**。それには数ヶ月かかることがあります。フレームワークによって数日でMVPを起動できますが、長期的にはそれらは不要な複雑性と認知負荷を追加する傾向があります。\n\nさらに悪いことに、ある時点でフレームワークは、アーキテクチャに合わない新しい要件に直面したときに重大な制約となる可能性があります。ここから人々はフレームワークをフォークして独自のカスタム版を維持することになります。新参者が価値を提供するためにどのくらいの認知負荷を構築（つまり、このカスタムフレームワークを学習）する必要があるか想像してみてください。`🤯`\n\n**決してすべてをゼロから発明することを提唱しているのではありません！**\n\n私たちは多少フレームワークに依存しない方法でコードを書くことができます。ビジネスロジックはフレームワーク内に存在すべきではありません。むしろ、フレームワークのコンポーネントを使用すべきです。フレームワークをコアロジックの外側に置きます。フレームワークをライブラリのような方法で使用します。これにより、新しい貢献者はまずフレームワーク関連の複雑性の層をかいくぐる必要なしに、初日から価値を追加できるようになります。\n\n> [Why I Hate Frameworks](https://minds.md/benji/frameworks)\n\n## レイヤードアーキテクチャ\nこうしたエンジニアリングのすべてに、ある種の興奮があります。\n\n私自身、何年もヘキサゴナル/オニオンアーキテクチャの情熱的な提唱者でした。あちこちで使用し、他のチームにも勧めました。プロジェクトの複雑性が上がり、ファイル数だけでも倍増しました。多くのグルーコードを書いているように感じました。絶え間なく変化する要件に対して、複数の抽象化レイヤーにわたって変更を行う必要があり、結局それはすべて面倒になりました。`🤯`\n\n抽象化は複雑性を隠すことになっていますが、ここではただ[間接性](https://fhur.me/posts/2024/thats-not-an-abstraction)を追加しているだけです。何が悪くて何が欠けているかを把握するために、呼び出しから呼び出しへとジャンプしなければならないのは、問題を迅速に解決する上で重要でありながら、負担が大きいです。このアーキテクチャのレイヤー分離では、障害が発生するポイントにたどり着くために余計な手間が指数関数的に増え、トレースも断片的になりがちです。そのようなトレースはそれぞれ、私たちの限られたワーキングメモリのスペースを占有します。`🤯`\n\nこのアーキテクチャは最初は直感的に理にかなっているように見えましたが、プロジェクトに適用しようとするたびに、良いことよりもはるかに害をなしました。最終的に、古き良き依存関係逆転原則(DIP)を支持して、すべてを諦めました。**学ぶべきポートやアダプター、不要な水平抽象化、外在的認知負荷、それらは必要ありません**\n\n<details>\n  <summary><b>コーディング原則と経験</b></summary>\n  <img src=\"img/complexity.png\"><br>\n  <a href=\"https://twitter.com/flaviocopes\">@flaviocopes</a>\n</details>\n\nそのような階層化によってデータベースや他の依存関係を迅速に置き換えることができると思うなら、それは間違いです。ストレージを変更すると多くの問題が発生し、信じてください、データアクセス層にいくつかの抽象化を持つことは最小の心配事です。せいぜい、抽象化は移行時間の約10％（もしあれば）を節約できますが、実際の痛みはデータモデルの非互換性、通信プロトコル、分散システムの課題、そして[暗黙のインターフェース](https://www.hyrumslaw.com)にあります。\n\n> APIの十分なユーザー数があれば、\n> 契約で何を約束しようと関係ありません：\n> システムのすべての観察可能な動作が\n> 誰かによって依存されます。\n\n私たちはストレージ移行を行い、約10ヶ月かかりました。古いシステムはシングルスレッドだったので、公開されるイベントは順次でした。私たちのすべてのシステムがその観察された動作に依存していました。この動作はAPIコントラクトの一部ではなく、コードに反映されていませんでした。新しい分散ストレージにはその保証がありませんでした - イベントは順不同で来ました。抽象化のおかげで、新しいストレージアダプターのコーディングには数時間しかかかりませんでした。**順不同イベントやその他の課題への対処に次の10ヶ月を費やしました。** 抽象化がコンポーネントを迅速に置き換える助けになるというのは今では面白いことです。\n\n**では、そのようなレイヤードアーキテクチャの高い認知負荷の代価を支払うのはなぜでしょうか、それが将来報われないのであれば？** さらに、ほとんどの場合、いくつかのコアコンポーネントを置き換えるその未来は決して起こりません。\n\nこれらのアーキテクチャは基本的ではなく、より基本的な原則の主観的で偏った結果にすぎません。なぜそれらの主観的な解釈に依存するのでしょうか？代わりに基本的な規則に従いましょう：依存関係逆転原則、単一の真実の源、認知負荷、情報隠蔽。私たちのビジネスロジックは、データベース、UI、フレームワークなどの低レベルモジュールに依存すべきではありません。インフラストラクチャを心配することなくコアロジックのテストを書けるべきで、それだけです。[議論](https://github.com/zakirullin/cognitive-load/discussions/24)。\n\nアーキテクチャのために抽象化レイヤーを追加してはいけません。実用的な理由で正当化される拡張ポイントが必要なときにそれらを追加しましょう。\n\n**[抽象化レイヤーは無料ではありません](https://blog.jooq.org/why-you-should-not-implement-layered-architecture)、それらは私たちの限られたワーキングメモリに保持される必要があります**。\n\n<div align=\"center\">\n  <img src=\"/img/layers.png\" alt=\"レイヤー\" width=\"400\">\n</div>\n\n## ドメイン駆動設計\nドメイン駆動設計にはいくつかの素晴らしいポイントがありますが、しばしば誤解されています。人々は「私たちはDDDでコードを書く」と言いますが、これは少し奇妙です。なぜならDDDはソリューション空間よりも問題空間に関するものだからです。\n\nユビキタス言語、ドメイン、境界づけられたコンテキスト、集約、イベントストーミングはすべて問題空間に関するものです。それらはドメインについての洞察を学び、境界を抽出するのを助けることを意図しています。DDDは開発者、ドメインエキスパート、ビジネス関係者が単一の統一された言語を使って効果的にコミュニケーションを取ることを可能にします。DDDのこれらの問題空間の側面に焦点を当てる代わりに、私たちは特定のフォルダ構造、サービス、リポジトリ、その他のソリューション空間技術を強調する傾向があります。\n\n私たちがDDDを解釈する方法が独特で主観的である可能性があります。そして、この理解の上にコードを構築する場合、つまり、多くの外在的認知負荷を作り出す場合 - 将来の開発者は運命づけられています。`🤯`\n\nTeam Topologiesは、認知負荷をチーム間で分割するのに役立つはるかに良い、理解しやすいフレームワークを提供します。エンジニアはTeam Topologiesについて学んだ後、多少似たような心的モデルを開発する傾向があります。一方、DDDは10人の異なる読者に対して10の異なる心的モデルを作り出すように見えます。共通の基盤になる代わりに、不要な議論の戦場になります。\n\n## 馴染みのあるプロジェクトでの認知負荷\n\n> 問題は、**馴染みやすさが単純さと同じではない**ことです。それらは同じように*感じます* - 同じような精神的努力なしに空間を移動するその同じ容易さ - しかし非常に異なる理由によるものです。あなたが使用するすべての「賢い」（読む：「自己満足的な」）で非慣用的なトリックは、他のすべての人に学習ペナルティを課します。彼らがその学習を行うと、コードを扱うのが困難でなくなります。だから、すでに馴染みのあるコードを簡潔にする方法を認識するのは困難です。これが私が「新人」にあまりにも制度化される前にコードを批評してもらうようにする理由です！\n>\n> 前の著者がこの巨大な混乱を一度にではなく、一つの小さな増分ずつ作り出した可能性があります。だから、あなたが一度にすべてを理解しようとする最初の人なのです。\n>\n> 私のクラスでは、ある日見ていた広がりのあるSQL格納プロシージャを説明します。巨大なWHERE句に何百行の条件文がありました。誰かがどうしてこんなにひどくなるのを許したのかと尋ねました。私は彼らに言いました：「2つや3つの条件しかないときに、もう一つ追加してもどんな違いも生みません。20や30の条件があるときに、もう一つ追加してもどんな違いも生みません！」\n>\n> あなたが行う意図的な選択以外に、コードベースに作用する「簡素化力」はありません。簡素化には努力が必要で、人々はあまりにもしばしば急いでいます。\n>\n> *[Dan North](https://dannorth.net)のコメントに感謝*。\n\nプロジェクトの心的モデルを長期記憶に内在化していれば、高い認知負荷を経験することはないでしょう。\n\n<div align=\"center\">\n  <img src=\"/img/mentalmodelsv15.png\" alt=\"心的モデル\" width=\"700\">\n</div>\n\n学ぶべき心的モデルが多いほど、新しい開発者が価値を提供するのに時間がかかります。\n\nプロジェクトに新しい人をオンボードするときは、彼らが持つ混乱の量を測定してみてください（ペアプログラミングが役立つかもしれません）。40分以上連続して混乱している場合 - コードで改善すべきことがあります。\n\n認知負荷を低く保てば、人々は会社に参加してから最初の数時間以内にコードベースに貢献できます。\n\n## 例\n- 私たちのアーキテクチャは標準的なCRUDアプリアーキテクチャ、[PostgreSQLの上のPythonモノリス](https://danluu.com/simple-architectures/)\n- Instagramが[わずか3人のエンジニア](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)で1400万ユーザーまでスケールした方法\n- 私たちが「うわあ、この人たちは[めちゃくちゃ賢い](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)」と思った企業の大部分は失敗した\n- システム全体を配線する一つの関数。システムがどのように動作するかを知りたいなら - [それを読みに行く](https://www.infoq.com/presentations/8-lines-code-refactoring)\n\nこれらのアーキテクチャは非常に退屈で理解しやすいです。誰でも大きな精神的努力なしにそれらを把握できます。\n\nアーキテクチャレビューにジュニア開発者を関与させましょう。彼らは精神的に負荷の大きい領域を特定するのに役立ちます。\n\n**ソフトウェアの維持は困難です**、物事は壊れ、節約できるすべての精神的努力が必要になります。\n\n## 結論\nちょっと想像してみてください。もし第2章で推論したことが実は正しくなかったとしたらどうなるでしょう？ その場合、いま否定した結論だけでなく、これまで正しいと受け入れてきた前章の結論までも、正しくないかもしれません。🤯\n\n実感できますか？ 意味を理解するために記事のあちこちを行き来しなければならず（＝浅いモジュール！）、しかも、全体としてとても分かりにくい段落になっています。私たちは、あなたの頭に余計な認知負荷をつくり出してしまったのです。**同僚にこのようなことをしてはいけません。**\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"賢い著者\" width=\"600\">\n</div>\n\n私たちが行う仕事の本質的なものを超えるあらゆる認知負荷を減らすべきです。\n\n---\n[LinkedIn](https://www.linkedin.com/in/zakirullin/)、[X](https://twitter.com/zakirullin)、[GitHub](https://github.com/zakirullin)\n\n[読みやすい版](https://minds.md/zakirullin/cognitive)\n\n<details>\n    <summary><b>コメント</b></summary>\n    <br>\n    <p><strong>Rob Pike</strong><br>良い記事です。</p>\n    <p><strong><a href=\"https://x.com/karpathy/status/1872038630405054853\" target=\"_blank\">Andrej Karpathy</a></strong> <i>(ChatGPT、Tesla)</i><br>ソフトウェアエンジニアリングに関する良い投稿。おそらく最も真実で、最も実践されていない観点。</p>\n    <p><strong><a href=\"https://x.com/elonmusk/status/1872346903792566655\" target=\"_blank\">Elon Musk</a></strong><br>真実だ。</p>\n    <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>\n    <p>皮肉なことに？これらの複雑性を誘発するパターンの多くは「クリーンコード」の名の下に実装されていました。</p>\n    <p>本当に重要なのは、不要な認知負荷を減らすことです。時には多くの浅いモジュールではなく、より少ない深いモジュールを意味することもあります。時には小さな関数に分割するのではなく、関連するロジックを一緒に保つことを意味することもあります。</p>\n    <p>そして時には賢いものよりも退屈で素直な解決策を選ぶことを意味します。最高のコードは最もエレガントで洗練されたものではありません - 将来の開発者（あなた自身を含む）が迅速に理解できるコードです。</p>\n    <p>あなたの記事は、ブラウザ開発で私たちが直面する課題と本当に共鳴します。認知負荷について言った多くのポイントと完璧に一致する、現代のブラウザが最も複雑なソフトウェアシステムの一つであることについて、あなたは絶対に正しいです。</p>\n    <p>Chromiumでこれを処理しようとする一つの方法は、慎重なコンポーネント分離とサブシステム間（レンダリング、ネットワーキング、JavaScript実行など）の明確に定義されたインターフェースです。Unix I/Oでの深いモジュールの例と似ています - 比較的シンプルなインターフェースの背後にある強力な機能を目指しています。例えば、私たちのレンダリングパイプラインは信じられないほどの複雑性（レイアウト、合成、GPUアクセラレーション）を処理しますが、開発者は明確な抽象化レイヤーを通してそれと相互作用できます。</p>\n    <p>不要な抽象化を避けることについてのあなたのポイントも本当に心に響きます。ブラウザ開発では、新しい貢献者にとってコードベースをアプローチしやすくすることとウェブ標準と互換性の本質的な複雑性を処理することの間で常にバランスを取っています。</p>\n    <p>時には、複雑なシステムであっても、最も単純な解決策が最良の解決策です。</p>\n    <p><strong><a href=\"https://x.com/antirez\" target=\"_blank\">antirez</a></strong> <i>(Redis)</i><br>それについて完全に同意します :) また、私が信じているのは、言及された「A Philosophy of Software Design」から欠けているのは「設計犠牲」の概念です。つまり、時には何かを犠牲にして、シンプルさ、またはパフォーマンス、またはその両方を取り戻すのです。私はこのアイデアを継続的に適用していますが、しばしば理解されません。</p>\n    <p>良い例は、私がハッシュアイテムの期限切れを持つことを常に拒否したことです。これは設計犠牲です。なぜなら、特定の属性をトップレベルアイテム（キー自体）にのみ持つ場合、設計はよりシンプルになり、値は単なるオブジェクトになります。Redisがハッシュ期限切れを得たとき、それは良い機能でしたが、実際に多くの部分に多くの変更を必要とし、複雑性を高めました。</p>\n    <p>別の例は、私が今やっていること、Vector Sets、新しいRedisデータタイプです。私はRedisがベクトルについての真実の源ではなく、それらの近似版を取ることができるだけであると決定したので、ディスク上の大きなフロートベクトルを保持しようとすることなく、挿入時の正規化、量子化を行うことができました。多くのベクトルDBは、ユーザーが入れたもの（完全精度ベクトル）を覚えているという事実を犠牲にしません。</p>\n    <p>これらは単なる2つのランダムな例ですが、このアイデアをどこでも適用しています。今のことは：もちろん正しいものを犠牲にしなければなりません。しばしば、非常に大きな複雑性を占める5％の機能があります：それを殺すのは良いことです :D</p>\n    <p><strong><a href=\"https://working-for-the-future.medium.com/about\" target=\"_blank\">インターネットからの開発者</a></strong><br>あなたは私を雇わないでしょう... 私はリリースされたエンタープライズプロジェクトの実績で自分を売り込みます。</p>\n    <p>設計パターンを話すことができる人と働いたことがあります。私はそのように話すことは決してできませんでしたが、彼を理解できる数少ない人の一人でした。マネージャーは彼を愛し、彼はあらゆる開発会話を支配することができました。彼の周りで働く人々は、彼が後ろに破壊の跡を残していると言いました。私は彼のプロジェクトを理解できる最初の人だと言われました。メンテナビリティは重要です。私は最もTCOを気にします。いくつかの企業にとって、それが重要なことです。</p>\n    <p>しばらくGithubに行っていなかった後にログインし、なぜか無作為に見える誰かによるリポジトリ内の記事に連れて行かれました。「これは何だ」と思って、ホームページにたどり着くのに少し問題があったので、それを読みました。その時は実際には登録していませんでしたが、それは素晴らしいものでした。すべての開発者が読むべきです。それは基本的に、プログラミングのベストプラクティスについて私たちが言われてきたことのほぼすべてが過度の「認知負荷」につながると言っていました。つまり、私たちの心が知的要求によって蹴られているということです。私はしばらくの間これを知っていました、特にクラウド、セキュリティ、DevOpsの要求で。</p>\n    <p>また、それが何十年もやってきた実践について説明していたので気に入りました、しかし人気がないためにあまり認めることはありません... 私は本当に複雑なものを書き、すべてのヘルプが得られる必要があります。</p>\n    <p>考えてみると、私が正しければ、それはGithubの人々、非常に賢い人々が開発者がそれを見るべきだと思ったために現れました。私も同意します。</p>\n    <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>\n</details>\n"
  },
  {
    "path": "README.ko.md",
    "content": "# 인지 부하가 중요합니다\n\n## 소개 (Introduction)\n세상에는 수많은 유행어와 모범 사례가 있지만, 대부분은 실패했습니다. 우리에게는 더 근본적인 것, 틀릴 수 없는 무언가가 필요합니다.\n\n때때로 우리는 코드를 살펴보면서 혼란을 느낍니다. 혼란은 시간과 비용을 초래합니다. 혼란은 높은 *인지 부하*로 인해 발생합니다. 높은 인지 부하는 화려하고 추상적인 개념이 아니라 **근본적인 인간의 제약**입니다. 상상 속의 존재가 아니라 실제로 존재하며 우리가 느낄 수 있는 것입니다.\n\n우리는 코드를 작성하는 시간보다 읽고 이해하는 데 훨씬 더 많은 시간을 소비하므로, 코드에 과도한 인지 부하를 심고 있지는 않은지 끊임없이 자문해야 합니다.\n\n## 인지 부하 (Cognitive load)\n> 인지 부하란 개발자가 작업을 완료하기 위해 생각해야 하는 양입니다.\n\n우리는 코드를 읽을 때 변수 값, 제어 흐름 논리, 호출 순서와 같은 것들을 머릿속에 넣습니다. 평균적인 사람은 작업 기억에 대략 [4개의 덩어리](https://github.com/zakirullin/cognitive-load/issues/16)를 담을 수 있습니다. 인지 부하가 작업 기억의 임계값에 도달하면 내용을 이해하기가 훨씬 더 어려워집니다.\n\n*완전히 낯선 프로젝트의 수정 요청을 받았다고 가정해 봅시다. 정말 똑똑한 개발자가 이 프로젝트에 기여했다고 들었습니다. 수많은 멋진 아키텍처, 화려한 라이브러리, 유행하는 기술이 사용되었습니다. 다시 말해, **작성자가 우리에게 높은 인지 부하를 안겨준 것입니다.***\n\n<div align=\"center\">\n  <img src=\"/img/cognitiveloadv6.png\" alt=\"인지 부하\" width=\"750\">\n</div>\n\n우리는 프로젝트에서 인지 부하를 최대한 줄여야 합니다.\n\n<details>\n  <summary><b>인지 부하와 방해 요소</b></summary>\n  <img src=\"img/interruption.jpeg\"><br>\n</details>\n\n## 인지 부하의 유형 (Types of cognitive load)\n**내재적 인지 부하** - 작업 자체의 고유한 어려움으로 인해 발생합니다. 줄일 수 없으며, 소프트웨어 개발의 핵심에 있습니다.\n\n**외재적 인지 부하** - 정보가 제시되는 방식으로 인해 발생합니다. 똑똑한 작성자의 기행과 같이 작업과 직접 관련 없는 요인으로 인해 발생합니다. 크게 줄일 수 있습니다. 이 글에서는 이 외재적 인지 부하에 초점을 맞출 것입니다.\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"내재적 대 외재적\" width=\"600\">\n</div>\n\n외재적 인지 부하의 구체적인 실제 사례로 바로 넘어가 보겠습니다.\n\n---\n\n인지 부하 수준은 다음과 같이 표현합니다:  \n`🧠`: 새로운 작업 기억, 인지 \n부하 없음  \n`🧠++`: 작업 기억에 두 가지\n사실, 인지 부하 증가  \n`🤯`: 인지 과부하, 4가지 이상의 사실\n\n> 인간의 뇌는 훨씬 더 복잡하고 미지의 영역이지만, 이 글에서는 이 단순화된 모델을 사용합니다.\n\n## 복잡한 조건문 (Complex conditionals)\n```go\nif val > someConstant // 🧠+\n    && (condition2 || condition3) // 🧠+++, 이전 조건은 참이어야 하고, c2 또는 c3 중 하나는 참이어야 함\n    && (condition4 && !condition5) { // 🤯, 이 시점에서 우리는 혼란에 빠짐\n    ...\n}\n```\n\n의미 있는 이름을 가진 중간 변수를 도입합니다.\n```go\nisValid = val > someConstant\nisAllowed = condition2 || condition3\nisSecure = condition4 && !condition5\n// 🧠, 조건을 기억할 필요가 없음, 설명적인 변수가 있음\nif isValid && isAllowed && isSecure {\n    ...\n}\n```\n\n## 중첩된 if 문 (Nested ifs)\n```go\nif isValid { // 🧠+, 유효한 입력에만 중첩된 코드 적용\n    if isSecure { // 🧠++, 유효하고 안전한 입력에 대해서만 작업 수행\n        stuff // 🧠+++\n    }\n}\n```\n\n조기 반환과 비교해 보세요.\n```go\nif !isValid\n    return\n\nif !isSecure\n    return\n\n// 🧠, 이전 반환은 신경 쓰지 않음, 여기까지 왔다면 모든 것이 좋음\n\nstuff // 🧠+\n```\n\n우리는 행복한 경로에만 집중할 수 있으므로, 온갖 종류의 전제 조건으로부터 작업 기억을 해방시킬 수 있습니다.\n\n## 상속의 악몽 (Inheritance nightmare)\n관리자 사용자를 위해 몇 가지 사항을 변경해 달라는 요청을 받았습니다: `🧠`\n\n`AdminController extends UserController extends GuestController extends BaseController`\n\n아, 기능의 일부가 `BaseController`에 있군요, 한번 봅시다: `🧠+`  \n기본적인 역할 메커니즘은 `GuestController`에 도입되었습니다: `🧠++`  \n`UserController`에서 일부 내용이 변경되었습니다: `🧠+++`  \n드디어 `AdminController`입니다, 코딩합시다! `🧠++++`  \n\n아, 잠깐, `AdminController`를 확장하는 `SuperuserController`가 있습니다. `AdminController`를 수정하면 상속된 클래스에서 문제가 발생할 수 있으므로, 먼저 우리는 `SuperuserController`를 살펴봐야 합니다: `🤯`\n\n상속보다는 합성을 선호하세요. 자세한 내용은 다루지 않겠습니다. 이미 [많은 자료](https://www.youtube.com/watch?v=hxGOiiR9ZKg)가 있습니다.\n\n## 너무 많은 작은 메서드, 클래스 또는 모듈 (Too many small methods, classes or modules)\n> 이 맥락에서 메서드, 클래스, 모듈은 서로 바꿔 사용할 수 있습니다.\n\n\"메서드는 15줄 미만이어야 한다\" 또는 \"클래스는 작아야 한다\"와 같은 만트라는 다소 잘못된 것으로 밝혀졌습니다.\n\n**깊은 모듈** - 간단한 인터페이스, 복잡한 기능   \n**얕은 모듈** - 인터페이스가 제공하는 작은 기능에 비해 상대적으로 복잡함\n\n<div align=\"center\">\n  <img src=\"/img/deepmodulev8.png\" alt=\"깊은 모듈\" width=\"700\">\n</div>\n\n얕은 모듈이 너무 많으면 프로젝트를 이해하기 어려울 수 있습니다. **각 모듈의 책임뿐만 아니라 모든 상호 작용까지 염두에 두어야 합니다.** 얕은 모듈의 목적을 이해하려면 먼저 관련된 모든 모듈의 기능을 살펴봐야 합니다. `🤯`\n\n> 정보 은닉은 가장 중요하며, 얕은 모듈에서는 복잡성을 그다지 많이 숨기지 않습니다.\n\n필자는 두 개의 개인 프로젝트를 가지고 있는데, 둘 다 약 5,000줄의 코드입니다. 첫 번째 프로젝트에는 80개의 얕은 클래스가 있고, 두 번째 프로젝트에는 7개의 깊은 클래스만 있습니다. 1년 반 동안 이 두 프로젝트를 유지보수하지 않았습니다.\n\n돌아와서 보니 첫 번째 프로젝트에서 80개 클래스 간의 모든 상호 작용을 푸는 것이 극도로 어렵다는 것을 깨달았습니다. 코딩을 시작하기 전에 엄청난 양의 인지 부하를 다시 구축해야 했습니다. 반면에 두 번째 프로젝트는 간단한 인터페이스를 가진 몇 개의 깊은 클래스만 있었기 때문에 빠르게 파악할 수 있었습니다.\n\n> 최고의 구성 요소는 강력한 기능을 제공하면서도 간단한 인터페이스를 가진 것입니다.\n> **존 K. 아우스터하우트**\n\n유닉스 I/O의 인터페이스는 매우 간단합니다. 다섯 가지 기본 호출만 있습니다.\n```python\nopen(path, flags, permissions)\nread(fd, buffer, count)\nwrite(fd, buffer, count)\nlseek(fd, offset, referencePosition)\nclose(fd)\n```\n\n이 인터페이스의 최신 구현에는 **수십만 줄의 코드**가 있습니다. 많은 복잡성이 내부적으로 숨겨져 있습니다. 그러나 간단한 인터페이스 덕분에 사용하기 쉽습니다.\n\n> 이 깊은 모듈 예제는 존 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).\n\nP.S. 만약 이 글이 너무 많은 책임을 가진 비대한 갓 오브젝트를 지지한다고 생각한다면, 잘못된 생각입니다.\n\n## 한 가지 책임 (Responsible for one thing)\n너무나 자주, 우리는 \"모듈은 한 가지, 오직 한 가지 일에만 책임져야 한다\"는 모호한 원칙에 따라 수많은 얕은 모듈을 만들게 됩니다. 이 흐릿한 한 가지란 무엇일까요? 객체를 인스턴스화하는 것은 한 가지 일이죠, 그렇죠? 그러니 [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks)는 괜찮아 보입니다. 그런 클래스의 이름과 인터페이스는 전체 구현보다 정신적으로 더 부담스러운데, 그게 무슨 추상화일까요? 뭔가 잘못되었습니다.\n\n> 그런 얕은 구성 요소 사이를 오가는 것은 정신적으로 지치며, [선형적 사고](https://blog.separateconcerns.com/2023-09-11-linear-code.html)가 우리 인간에게 더 자연스럽습니다.\n\n우리는 사용자와 이해 관계자를 만족시키기 위해 시스템을 변경합니다. 우리는 그들에게 책임이 있습니다.\n\n> 모듈은 한 명, 오직 한 명의 사용자 또는 이해 관계자에게만 책임져야 합니다.\n\n이것이 바로 단일 책임 원칙의 전부입니다. 간단히 말해, 한 곳에서 버그를 발생시켰는데 두 명의 다른 비즈니스 담당자가 불평하러 온다면, 우리는 원칙을 위반한 것입니다. 이는 모듈에서 수행하는 작업의 수와는 아무런 관련이 없습니다.\n\n하지만 지금도 이 규칙은 득보다 실이 많을 수 있습니다. 이 원칙은 개인의 수만큼이나 다양한 방식으로 이해될 수 있습니다. 더 나은 접근 방식은 이러한 원칙 적용이 얼마나 많은 인지 부하를 발생시키는지 살펴보는 것입니다. 한 곳에서의 변경이 여러 다른 비즈니스 흐름에 걸쳐 연쇄 반응을 일으킬 수 있다는 것을 기억하는 것은 정신적으로 부담스럽습니다. 그리고 그것이 핵심입니다. 배울 만한 화려한 용어는 없습니다.\n\n## 너무 많은 얕은 마이크로서비스 (Too many shallow microservices)\n이 얕고 깊은 모듈 원칙은 규모에 구애받지 않으며, 마이크로서비스 아키텍처에 적용할 수 있습니다. 너무 많은 얕은 마이크로서비스는 아무런 도움이 되지 않습니다. 업계는 다소 \"매크로서비스\", 즉 그렇게 얕지 않은(=깊은) 서비스로 향하고 있습니다. 가장 최악이고 수정하기 어려운 현상 중 하나는 소위 분산 모놀리스인데, 이는 종종 이러한 지나치게 세분화된 얕은 분리의 결과입니다.\n\n필자는 한때 5명의 개발자로 구성된 팀이 17(!)개의 마이크로서비스를 도입한 스타트업에 컨설팅을 한 적이 있습니다. 이 팀은 예정보다 10개월 늦었고 공개 시점에 가까워 보이지 않았습니다. 새로운 요구 사항이 있을때마다 4개 이상의 마이크로서비스의 변경으로 이어졌습니다. 통합된 영역에서의 진단 난이도는 급증했습니다. 공개 시점과 인지 부하 모두 용납할 수 없을 정도로 높았습니다. `🤯`\n\n이러한 접근 방식이 새로운 시스템의 불확실성에 접근하는 올바른 방법일까요? 처음부터 올바른 논리적 경계를 도출하는 것은 엄청나게 어렵습니다. 핵심은 책임감 있게 기다릴 수 있는 한 결정을 최대한 늦추는 것입니다. 왜냐하면 그때가 결정을 내리는 데 필요한 가장 많은 정보를 가지고 있기 때문입니다. 처음부터 네트워크 계층을 도입함으로써 우리는 설계 결정을 처음부터 되돌리기 어렵게 만듭니다. 팀의 유일한 정당화는 \"FAANG 기업들이 마이크로서비스 아키텍처가 효과적이라는 것을 증명했다\"는 것이었습니다. *이봐요, 큰 꿈은 그만 꾸세요.*\n\n[타넨바움-토발즈 논쟁](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate)은 리눅스의 모놀리식 설계가 결함이 있고 구식이며, 마이크로커널 아키텍처를 사용해야 한다고 주장했습니다. 실제로 마이크로커널 설계는 \"이론적이고 미적인\" 관점에서 우월해 보였습니다. 실용적인 측면에서는 30년이 지난 지금도 마이크로커널 기반 GNU Hurd는 여전히 개발 중이며, 모놀리식 리눅스는 어디에나 있습니다. 이 페이지는 리눅스로 구동되며, 여러분의 스마트 주전자도 리눅스로 구동됩니다. 모놀리식 리눅스로 말이죠.\n\n진정으로 격리된 모듈을 갖춘 잘 만들어진 모놀리스는 종종 여러 마이크로서비스보다 훨씬 유연합니다. 또한 유지 관리에 훨씬 적은 인지적 노력이 필요합니다. 개발팀 확장과 같이 별도의 배포 필요성이 중요해질 때만 모듈, 즉 미래의 마이크로서비스 사이에 네트워크 계층을 추가하는 것을 고려해야 합니다.\n\n## 기능이 풍부한 언어 (Feature-rich languages)\n우리는 좋아하는 언어에 새로운 기능이 출시될 때 흥분을 느낍니다. 우리는 이러한 기능을 배우는 데 시간을 보내고, 새로운 기능을 기반으로 코드를 작성합니다.\n\n기능이 많으면 한두 가지 기능을 사용하기 위해 몇 줄의 코드를 가지고 30분을 보낼 수도 있습니다. 그리고 그러한 시간 소모는 낭비입니다. 하지만 더 나쁜 것은 **나중에 돌아왔을 때 그 사고 과정을 다시 만들어야 한다는 것입니다!**\n\n**이 복잡한 프로그램을 이해해야 할 뿐만 아니라, 프로그래머가 사용 가능한 기능 중에서 왜 이런 방식으로 문제에 접근하기로 결정했는지 이해해야 합니다.** `🤯`\n\n이런 주장은 다름 아닌 롭 파이크가 한 것입니다.\n\n> 선택의 수를 제한하여 인지 부하를 줄이십시오.\n\n언어 기능은 서로 직교하는 한 괜찮습니다.\n\n<details>\n  <summary><b>C++ 경력 20년 엔지니어의 생각 ⭐️</b></summary>\n  <br>\n  며칠 전 RSS 리더를 보다가 \"C++\" 태그 아래에 읽지 않은 기사가 300개 정도 있다는 것을 알았습니다. 지난 여름 이후로 언어에 대한 기사를 단 한 편도 읽지 않았는데, 기분이 아주 좋습니다!<br><br>\n  저는 지금까지 20년 동안 C++를 사용해 왔는데, 이는 제 인생의 거의 3분의 2에 해당합니다. 제 경험의 대부분은 언어의 가장 어두운 구석(예: 온갖 종류의 정의되지 않은 동작)을 다루는 데 있습니다. 재사용 가능한 경험이 아니며, 지금 모든 것을 버리는 것은 좀 소름 끼치는 일입니다.<br><br>\n  상상해 보세요. <code>||</code> 토큰은 <code>requires ((!P&lt;T&gt; || !Q&lt;T&gt;))</code>와 <code>requires (!(P&lt;T&gt; || Q&lt;T&gt;))</code>에서 다른 의미를 갖습니다. 첫 번째는 제약 조건 논리합이고, 두 번째는 예전의 논리 OR 연산자이며, 다르게 동작합니다.<br><br>\n  단순한 유형에 대한 공간을 할당하고 거기에 바이트 집합을 <code>memcpy</code>하는 것만으로는 추가적인 노력 없이 객체의 수명을 시작할 수 없습니다. 이것은 C++20 이전의 경우였습니다. C++20에서 수정되었지만 언어의 인지 부하는 증가했을 뿐입니다.<br><br>\n  문제가 해결되었음에도 불구하고 인지 부하는 지속적으로 증가하고 있습니다. 무엇이 수정되었는지, 언제 수정되었는지, 이전에는 어땠는지 알아야 합니다. 저는 결국 전문가입니다. 물론 C++는 레거시 지원이 훌륭하며, 이는 또한 여러분이 그 레거시에 <b>직면하게 될 것</b>을 의미합니다. 예를 들어, 지난달 동료가 C++03의 일부 동작에 대해 저에게 물었습니다. <code>🤯</code><br><br>\n  초기화 방법에는 20가지가 있었습니다. 균일 초기화 구문이 추가되었습니다. 이제 21가지 초기화 방법이 있습니다. 그런데 초기화 목록에서 생성자를 선택하는 규칙을 기억하는 사람이 있습니까? 정보 손실이 가장 적은 암시적 변환에 관한 것이지만, <i>만약</i> 값이 정적으로 알려져 있다면... <code>🤯</code><br><br>\n  <b>이렇게 증가된 인지 부하는 당면한 비즈니스 작업으로 인해 발생하는 것이 아닙니다. 도메인의 본질적인 복잡성이 아닙니다. 단지 역사적인 이유로 존재하는 것입니다</b>(<i>외재적 인지 부하</i>).<br><br>\n  몇 가지 규칙을 만들어야 했습니다. 예를 들어, 코드 줄이 명확하지 않고 표준을 기억해야 한다면 그렇게 작성하지 않는 것이 좋습니다. 참고로 표준은 약 1500페이지입니다.<br><br>\n  <b>결코 C++를 비난하려는 것이 아닙니다.</b> 저는 이 언어를 사랑합니다. 단지 지금은 지쳤을 뿐입니다.<br><br>\n  \n  <p>\n    <a href=\"https://0xd34df00d.me\" target=\"_blank\\\">0xd34df00d</a>님 작성 감사합니다.\n  </p>\n  \n  </details>\n\n\n## 비즈니스 로직과 HTTP 상태 코드 (Business logic and HTTP status codes)\n백엔드에서는 다음을 반환합니다.\n`401` 만료된 JWT 토큰\n`403` 접근 권한 부족\n`418` 차단된 사용자\n\n프론트엔드 엔지니어는 백엔드 API를 사용하여 로그인 기능을 구현합니다. 그들은 일시적으로 다음과 같은 인지 부하를 뇌에 만들어야 합니다:  \n`401`은 만료된 JWT 토큰입니다 // `🧠+`, 그냥 일시적으로 기억하세요  \n`403`은 접근 권한 부족입니다 // `🧠++`  \n`418`은 차단된 사용자입니다 // `🧠+++`  \n\n프론트엔드 개발자는 (바라건대) `숫자 상태 -> 의미` 사전을 만들어 후속 기여자들이 이 매핑을 뇌에서 다시 만들 필요가 없도록 할 것입니다.\n\n그런 다음 QA 엔지니어가 등장합니다.\n\\\"이봐요, `403` 상태를 받았는데, 토큰이 만료된 건가요, 아니면 접근 권한이 부족한 건가요?\\\"\n\n**QA 엔지니어는 백엔드 엔지니어가 한때 만들었던 인지 부하를 먼저 다시 만들어야 하기 때문에 바로 테스트를 시작할 수 없습니다.**\n\n왜 이러한 사용자 지정 매핑을 작업 기억에 담아두어야 할까요? 비즈니스 세부 정보를 HTTP 전송 프로토콜에서 추상화하고 응답 본문에 직접 자체 설명 코드를 반환하는 것이 좋습니다.\n```json\n{\n    \"code\": \"jwt_has_expired\"\n}\n```\n\n프론트엔드 측의 인지 부하: `🧠` (새로움, 마음에 담아둔 사실 없음)  \nQA 측의 인지 부하: `🧠`  \n\n데이터베이스나 어디에서든 모든 종류의 숫자 상태에 동일한 규칙이 적용됩니다. **자체 설명 문자열을 선호하세요.** 현대 개발 환경은 메모리를 최적화하기 위해 640K 컴퓨터 시대에 살고 있지 않습니다.\n\n> 사람들은 `401`과 `403` 사이에서 논쟁하며 자신의 멘탈 모델을 기반으로 결정을 내립니다. 새로운 개발자가 들어오고 새로운 개발자는 그 사고 과정을 다시 만들어야 합니다. 코드에 대한 \"이유\"(ADR)를 문서화하여 새로운 사람이 내린 결정을 이해하도록 도울 수 있습니다. 하지만 결국에는 아무 의미가 없습니다. 오류를 사용자 관련 또는 서버 관련으로 분리할 수 있지만, 그 외에는 상황이 다소 모호합니다.\n\n추신: \"인증\"과 \"권한 부여\"를 구별하는 것은 종종 정신적으로 부담스럽습니다. 인지 부하를 줄이기 위해 [\"로그인\" 및 \"권한\"](https://ntietz.com/blog/lets-say-instead-of-auth/)과 같은 더 간단한 용어를 사용할 수 있습니다.\n\n## DRY 원칙 남용 (Abusing DRY principle)\n\n반복하지 마십시오(Do not repeat yourself) - DRY 원칙은 소프트웨어 엔지니어로서 배우는 첫 번째 원칙 중 하나입니다. 우리 자신에게 너무 깊이 내재되어 있어서 몇 줄의 추가 코드를 참을 수 없습니다. 일반적으로 좋고 근본적인 규칙이지만, 과도하게 사용하면 감당할 수 없는 인지 부하로 이어집니다.\n\n요즘 모든 사람은 논리적으로 분리된 구성 요소를 기반으로 소프트웨어를 구축합니다. 종종 이러한 구성 요소는 별도의 서비스를 나타내는 여러 코드베이스에 분산되어 있습니다. 모든 반복을 제거하려고 노력하면 관련 없는 구성 요소 간에 긴밀한 결합이 발생할 수 있습니다. 결과적으로 한 부분의 변경이 다른 관련 없어 보이는 영역에 의도하지 않은 결과를 초래할 수 있습니다. 또한 전체 시스템에 영향을 주지 않고 개별 구성 요소를 교체하거나 수정하는 기능을 방해할 수 있습니다. `🤯`\n\n사실, 동일한 문제는 단일 모듈 내에서도 발생합니다. 장기적으로 실제로 존재하지 않을 수 있는 인식된 유사성을 기반으로 공통 기능을 너무 일찍 추출할 수 있습니다. 이로 인해 수정하거나 확장하기 어려운 불필요한 추상화가 발생할 수 있습니다.\n\n롭 파이크는 이렇게 말했습니다.\n\n> 약간의 복사본이 약간의 종속성보다 낫습니다.\n\n우리는 바퀴를 재발명하지 않으려는 유혹이 너무 강해서, 스스로 쉽게 작성할 수 있는 작은 함수를 사용하기 위해 크고 무거운 라이브러리를 가져올 준비가 되어 있습니다.\n\n**모든 종속성은 여러분의 코드입니다.** 가져온 라이브러리의 10개 이상의 스택 추적 수준을 살펴보고 무엇이 잘못되었는지 알아내는 것(*왜냐하면 문제가 발생하기 때문입니다*)은 고통스럽습니다.\n\n## 프레임워크와의 긴밀한 결합 (Tight coupling with frameworks)\n프레임워크에는 많은 \"마법\"이 있습니다. 프레임워크에 너무 많이 의존함으로써 **프로젝트는 다가올 모든 개발자에게 그 \"마법\"을 먼저 배우도록 강요합니다.** 몇 달이 걸릴 수 있습니다. 프레임워크를 사용하면 며칠 만에 MVP를 출시할 수 있지만, 장기적으로는 불필요한 복잡성과 인지 부하를 추가하는 경향이 있습니다.\n\n더 나쁜 것은, 어느 시점에서 프레임워크가 아키텍처에 맞지 않는 새로운 요구 사항에 직면했을 때 심각한 제약이 될 수 있다는 것입니다. 이 시점부터 사람들은 프레임워크를 포크하고 자체 사용자 지정 버전을 유지 관리하게 됩니다. 새로운 사람이 가치를 제공하기 위해 구축해야 하는 인지 부하의 양(즉, 이 사용자 지정 프레임워크를 배우는 것)을 상상해 보십시오. `🤯`\n\n**결코 모든 것을 처음부터 발명하라고 주장하는 것이 아닙니다!**\n\n우리는 다소 프레임워크에 구애받지 않는 방식으로 코드를 작성할 수 있습니다. 비즈니스 로직은 프레임워크 내에 있어서는 안 되며, 오히려 프레임워크의 구성 요소를 사용해야 합니다. 프레임워크를 핵심 로직 외부에 두십시오. 프레임워크를 라이브러리처럼 사용하십시오. 이러한 접근 방식은 새로운 기여자가 프레임워크 관련 복잡성의 잔해를 거칠 필요 없이 첫날부터 가치를 추가할 수 있습니다.\n\n> [내가 프레임워크를 싫어하는 이유](https://minds.md/benji/frameworks)\n\n## 계층형 아키텍처 (Layered architecture)\n이런 기술적인 주제엔 엔지니어로서의 확실한 설렘이 있다 (certain engineering excitement).\n\n필자도 수년 동안 Hexagonal/Onion 아키텍처의 열렬한 지지자였습니다. 여기저기서 사용했고 다른 팀에도 그렇게 하도록 권장했습니다. 필자가 참여한 프로젝트의 복잡성은 증가했고, 파일 수만 해도 두 배가 되었습니다. 마치 많은 접착 코드를 작성하는 것처럼 느껴졌습니다. 끊임없이 변화하는 요구 사항에 따라 여러 추상화 계층에 걸쳐 변경해야 했고, 모든 것이 지루해졌습니다. `🤯`\n\n추상화는 복잡성을 숨기기 위한 것이지만, 여기서는 단지 [간접성](https://fhur.me/posts/2024/thats-not-an-abstraction)을 추가할 뿐입니다. 호출에서 호출로 이동하여 읽고 무엇이 잘못되었고 무엇이 누락되었는지 파악하는 것은 문제를 신속하게 해결하기 위한 필수 요구 사항입니다. 이 아키텍처의 계층 분리는 실패가 발생하는 지점에 도달하기 위해 기하급수적으로 많은, 종종 분리된 추적을 필요로 합니다. 이러한 각 추적은 제한된 작업 기억 공간을 차지합니다. `🤯`\n\n이 아키텍처는 처음에는 직관적으로 이해가 되었지만, 프로젝트에 적용하려고 할 때마다 득보다 실이 훨씬 많았습니다. 결국 우리는 오래된 의존성 역전 원칙을 위해 모든 것을 포기했습니다. **배울 포트/어댑터 용어도 없고, 불필요한 수평적 추상화 계층도 없고, 외재적 인지 부하도 없습니다.**\n\n<details>\n  <summary><b>코딩 원칙과 경험</b></summary>\n  <img src=\"img/complexity.png\"><br>\n  <a href=\"https://twitter.com/flaviocopes\\\">@flaviocopes</a>\n</details>\n\n이러한 계층화가 데이터베이스나 다른 종속성을 신속하게 교체할 수 있게 해줄 것이라고 생각한다면 착각입니다. 스토리지를 변경하면 많은 문제가 발생하며, 데이터 액세스 계층에 대한 일부 추상화가 있다는 것은 걱정거리 중 가장 작은 부분이라는 것이 분명합니다. 기껏해야 추상화는 마이그레이션 시간의 약 10%를 절약할 수 있지만(있다면), 진짜 고통은 데이터 모델 비호환성, 통신 프로토콜, 분산 시스템 문제 및 [암시적 인터페이스](https://www.hyrumslaw.com)에 있습니다.\n\n> API 사용자가 충분히 많으면,\n> 계약서에 무엇을 약속하든 상관없습니다.\n> 시스템의 모든 관찰 가능한 동작은\n> 누군가에게 의존하게 될 것입니다.\n\n한 프로젝트에서는 스토리지 마이그레이션을 했고, 약 10개월이 걸렸습니다. 이전 시스템은 단일 스레드였기 때문에 노출된 이벤트는 순차적이었습니다. 모든 시스템이 관찰된 해당 동작에 의존했습니다. 이 동작은 API 계약의 일부가 아니었고 코드에 반영되지 않았습니다. 새로운 분산 스토리지는 그러한 보장이 없었습니다. 이벤트가 순서 없이 발생했습니다. 추상화 덕분에 새로운 스토리지 어댑터를 코딩하는 데 몇 시간밖에 걸리지 않았습니다. **다음 10개월은 순서 없는 이벤트 및 기타 문제를 처리하는 데 보냈습니다.** 이제 추상화가 구성 요소를 신속하게 교체하는 데 도움이 된다고 말하는 것은 우스꽝스럽습니다.\n\n**그렇다면 미래에 성과를 거두지 못한다면 왜 그러한 계층형 아키텍처에 대해 높은 인지 부하라는 대가를 치러야 할까요?** 게다가 대부분의 경우 일부 핵심 구성 요소를 교체하는 미래는 결코 오지 않습니다.\n\n이러한 아키텍처는 근본적인 것이 아니라 더 근본적인 원칙의 주관적이고 편향된 결과일 뿐입니다. 왜 그러한 주관적인 해석에 의존해야 할까요? 대신 근본적인 규칙을 따르십시오: 의존성 역전 원칙, 단일 진실 공급원, 인지 부하 및 정보 은닉. 비즈니스 로직은 데이터베이스, UI 또는 프레임워크와 같은 하위 수준 모듈에 의존해서는 안 됩니다. 인프라에 대해 걱정하지 않고 핵심 로직에 대한 테스트를 작성할 수 있어야 하며, 그것이 핵심입니다. [토론](https://github.com/zakirullin/cognitive-load/discussions/24).\n\n아키텍처를 위해 추상화 계층을 추가하지 마십시오. 실용적인 이유로 정당화되는 확장 지점이 필요할 때마다 추가하십시오.\n\n**[추상화 계층은 공짜가 아닙니다](https://blog.jooq.org/why-you-should-not-implement-layered-architecture). 제한된 작업 기억에 담아두어야 합니다.**\n\n<div align=\"center\">\n  <img src=\"/img/layers.png\" alt=\"계층\" width=\"400\">\n</div>\n\n## 도메인 주도 설계 (Domain-Driven Design)\n도메인 주도 설계(Domain-driven design)는 분명 훌륭한 개념들이 많지만, 종종 오해를 받기도 한다.\n사람들은 '우리는 DDD 방식으로 코드를 짠다'고 말하곤 하는데, 이는 다소 어색한 표현이다.\n왜냐하면 DDD는 '해결 방법(solution space)'이 아니라 '문제 영역(problem space)'에 대한 접근 방식이기 때문이다.\n\n유비쿼터스 언어, 도메인, 경계 컨텍스트, 집계, 이벤트 스토밍은 모두 '문제 영역(problem space)'에 관한 것입니다. 도메인에 대한 통찰력을 배우고 경계를 추출하는 데 도움이 되도록 만들어졌습니다. DDD를 통해 개발자, 도메인 전문가 및 비즈니스 담당자는 단일하고 통일된 언어를 사용하여 효과적으로 의사소통할 수 있습니다. DDD의 이러한 '문제 영역(problem space)' 측면에 초점을 맞추는 대신 특정 폴더 구조, 서비스, 리포지토리 및 기타 '해결 방법(solution space)' 기술을 강조하는 경향이 있습니다.\n\n각자가 DDD를 해석하는 방식은 독특하고 주관적일 가능성이 높습니다. 그리고 이러한 이해를 바탕으로 코드를 작성한다면, 즉 많은 외재적 인지 부하를 만든다면 미래의 개발자는 파멸할 것입니다. `🤯`\n\n팀 토폴로지는 팀 전체에 걸쳐 인지 부하를 분산하는 데 도움이 되는 훨씬 더 좋고 이해하기 쉬운 프레임워크를 제공합니다. 엔지니어는 팀 토폴로지에 대해 배운 후 다소 유사한 멘탈 모델을 개발하는 경향이 있습니다. 반면에 DDD는 10명의 다른 독자에게 10개의 다른 멘탈  모델을 만드는 것처럼 보입니다. 공통 기반이 되는 대신 불필요한 논쟁의 장이 됩니다.\n\n## 예시 (Examples)\n- 우리 아키텍처는 표준 CRUD 앱 아키텍처, [Postgres 기반 Python 모놀리스](https://danluu.com/simple-architectures/)입니다.\n- 인스타그램이 [단 3명의 엔지니어](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)로 1,400만 사용자까지 확장한 방법\n- 우리가 \"와, 이 사람들은 [정말 똑똑하네](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)\"라고 생각했던 회사들은 대부분 실패했습니다.\n- 전체 시스템을 연결하는 하나의 함수. 시스템이 어떻게 작동하는지 알고 싶다면 [읽어보세요](https://www.infoq.com/presentations/8-lines-code-refactoring).\n\n이러한 아키텍처는 매우 지루하고 이해하기 쉽습니다. 누구나 큰 정신적 노력 없이 파악할 수 있습니다.\n\n아키텍처 검토에 주니어 개발자를 참여시키십시오. 그들은 정신적으로 부담스러운 영역을 식별하는 데 도움을 줄 것입니다.\n\n## 익숙한 프로젝트의 인지 부하 (Cognitive load of familiar projects)\n\n> 문제는 **익숙함이 단순함과 같지 않다**는 것입니다. 익숙함과 단순함은 같은 느낌, 즉 별다른 정신적 노력 없이 공간을 쉽게 이동하는 느낌을 주지만, 매우 다른 이유 때문입니다. 사용하는 모든 \"영리한\"(즉, \"자기 만족적인\") 비관용적 트릭은 다른 모든 사람에게 학습 페널티를 부과합니다. 일단 그 학습을 마치면 코드로 작업하는 것이 덜 어렵다는 것을 알게 될 것입니다. 그래서 이미 익숙한 코드를 단순화하는 방법을 인식하기가 어렵습니다. 이것이 제가 \"신입\"이 너무 제도화되기 전에 코드를 비판하도록 하려는 이유입니다!\n>\n> 이전 작성자가 이 거대한 혼란을 한 번에 만든 것이 아니라 한 번에 조금씩 점진적으로 만들었을 가능성이 높습니다. 그래서 당신은 이 모든 것을 한 번에 이해하려고 노력한 첫 번째 사람입니다.\n>\n> 제 수업에서 참여자들은 어느 날 거대한 WHERE 절에 수백 줄의 조건문이 있는 광범위한 SQL 저장 프로시저를 보고 있었습니다. 누군가 어떻게 아무도 이렇게 나빠지도록 내버려 둘 수 있었는지 물었습니다. 저는 이렇게 말했습니다. \"조건문이 2~3개밖에 없을 때는 하나를 더 추가해도 아무런 차이가 없습니다. 조건문이 20~30개가 되면 하나를 더 추가해도 아무런 차이가 없습니다!\"\n>\n> 코드베이스에 작용하는 \"단순화하는 힘\"은 개발자가 내리는 의도적인 선택 외에는 없습니다. 단순화에는 노력이 필요하며, 사람들은 너무 자주 서두릅니다.\n>\n> *[댄 노스](https://dannorth.net)님의 의견 감사합니다.*\n\n프로젝트의 멘탈 모델을 장기 기억에 내재화했다면 높은 인지 부하를 경험하지 않을 것입니다.\n\n<div align=\"center\">\n  <img src=\"/img/mentalmodelsv15.png\" alt=\"멘탈 모델\" width=\"700\">\n</div>\n\n배워야 할 멘탈 모델이 많을수록 새로운 개발자가 가치를 제공하는 데 더 오랜 시간이 걸립니다.\n\n프로젝트에 새로운 사람을 온보딩할 때 그들이 겪는 혼란의 양을 측정해 보십시오(페어 프로그래밍이 도움이 될 수 있습니다). 만약 그들이 연속으로 40분 이상 혼란스러워한다면 코드에서 개선해야 할 부분이 있는 것입니다.\n\n인지 부하를 낮게 유지하면 사람들이 회사에 합류한 지 몇 시간 만에 코드베이스에 기여할 수 있습니다.\n\n## 결론 (Conclusion)\n두 번째 장에서 우리가 추론한 내용이 실제로는 사실이 아니라고 잠시 상상해 보십시오. 만약 그렇다면, 우리가 방금 부정한 결론과 이전 장에서 유효하다고 받아들였던 결론도 정확하지 않을 수 있습니다. `🤯`\n\n느껴지시나요? 의미를 파악하기 위해 기사 전체를 뛰어다녀야 할 뿐만 아니라(얕은 모듈!), 단락 자체가 이해하기 어렵습니다. 우리는 방금 여러분의 머릿속에 불필요한 인지 부하를 만들었습니다. **동료들에게 이런 짓을 하지 마십시오.**\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"똑똑한 저자\" width=\"600\">\n</div>\n\n우리는 개발 작업에 내재된 것 이상의 모든 인지 부하를 줄여야 합니다.\n\n---\n[링크드인](https://www.linkedin.com/in/zakirullin/), [X](https://twitter.com/zakirullin), [깃허브](https://github.com/zakirullin)\n\n[읽기 쉬운 버전](https://minds.md/zakirullin/cognitive)\n\n<details>\n    <summary><b>댓글</b></summary>\n    <br>\n    <p><strong>롭 파이크</strong><br>좋은 기사입니다.</p>\n    <p><strong><a href=\"https://x.com/karpathy/status/1872038630405054853\" target=\"_blank\">안드레이 카르파티</a></strong> <i>(ChatGPT, 테슬라)</i><br>소프트웨어 공학에 대한 좋은 글입니다. 아마도 가장 사실에 가깝지만 가장 실천되지 않는 관점일 것입니다.</p>\n    <p><strong><a href=\"https://x.com/elonmusk/status/1872346903792566655\" target=\"_blank\">일론 머스크</a></strong><br>사실입니다.</p>\n    <p><strong><a href=\"https://www.linkedin.com/feed/update/urn:li:activity:7277757844970520576/\" target=\"_blank\">애디 오스마니</a></strong> <i>(크롬, 세계에서 가장 복잡한 소프트웨어 시스템)</i><br>똑똑한 개발자들이 최신 디자인 패턴과 마이크로서비스를 사용하여 인상적인 아키텍처를 만든 수많은 프로젝트를 보았습니다. 하지만 새로운 팀원이 변경을 시도했을 때, 모든 것이 어떻게 맞춰지는지 이해하는 데만 몇 주를 보냈습니다. 인지 부하가 너무 높아 생산성이 급락하고 버그가 증식했습니다.</p>\n    <p>아이러니한 점은? 이러한 복잡성을 유발하는 패턴 중 다수가 \"클린 코드\"라는 이름으로 구현되었다는 것입니다.</p>\n    <p>정말로 중요한 것은 불필요한 인지 부담을 줄이는 것입니다. 때로는 이것이 많은 얕은 모듈 대신 더 적고 깊은 모듈을 의미하기도 합니다. 때로는 관련된 로직을 작은 함수로 나누는 대신 함께 유지하는 것을 의미하기도 합니다.</p>\n    <p>그리고 때로는 영리한 해결책보다 지루하고 간단한 해결책을 선택하는 것을 의미하기도 합니다. 최고의 코드는 가장 우아하거나 정교한 코드가 아니라 미래의 개발자(자신 포함)가 빠르게 이해할 수 있는 코드입니다.</p>\n    <p>당신의 기사는 우리가 브라우저 개발에서 직면하는 문제들과 정말로 공감됩니다. 현대 브라우저가 가장 복잡한 소프트웨어 시스템 중 하나라는 당신의 말은 절대적으로 옳습니다. 크로미움에서 그 복잡성을 관리하는 것은 인지 부하에 대해 당신이 지적한 많은 점들과 완벽하게 일치하는 끊임없는 도전입니다.</p>\n    <p>크로미움에서 이를 처리하는 한 가지 방법은 신중한 구성 요소 격리와 하위 시스템(렌더링, 네트워킹, 자바스크립트 실행 등) 간의 잘 정의된 인터페이스를 통하는 것입니다. 유닉스 I/O를 사용한 깊은 모듈 예제와 유사하게, 우리는 상대적으로 간단한 인터페이스 뒤에 강력한 기능을 목표로 합니다. 예를 들어, 우리의 렌더링 파이프라인은 엄청난 복잡성(레이아웃, 합성, GPU 가속)을 처리하지만 개발자는 명확한 추상화 계층을 통해 상호 작용할 수 있습니다.</p>\n    <p>불필요한 추상화를 피하는 것에 대한 당신의 지적도 정말 마음에 와 닿았습니다. 브라우저 개발에서 우리는 웹 표준 및 호환성의 고유한 복잡성을 처리하면서 새로운 기여자가 코드베이스에 접근하기 쉽게 만드는 것 사이에서 끊임없이 균형을 맞춥니다.</p>\n    <p>때로는 복잡한 시스템에서도 가장 간단한 해결책이 최선일 때가 있습니다.</p>\n    <p><strong><a href=\"https://x.com/antirez\" target=\"_blank\">antirez</a></strong> <i>(레디스)</i><br>완전히 동의합니다 :) 또한, 언급된 \"소프트웨어 설계 철학\"에서 빠졌다고 생각하는 것은 \"설계 희생\"이라는 개념입니다. 즉, 때로는 무언가를 희생하고 단순성이나 성능, 또는 둘 다를 얻을 수 있습니다. 저는 이 아이디어를 지속적으로 적용하지만 종종 이해받지 못합니다.</p>\n    <p>좋은 예는 제가 항상 해시 항목 만료를 거부했다는 사실입니다. 이것은 설계 희생입니다. 왜냐하면 최상위 항목(키 자체)에만 특정 속성이 있는 경우 설계가 더 간단해지고 값은 그냥 객체가 되기 때문입니다. 레디스에 해시 만료 기능이 추가되었을 때 좋은 기능이었지만 (실제로) 많은 부분에 많은 변경이 필요하여 복잡성이 증가했습니다.</p>\n    <p>또 다른 예는 제가 지금 하고 있는 벡터 세트, 새로운 레디스 데이터 유형입니다. 저는 레디스가 벡터에 대한 진실의 원천이 아니라 근사치 버전만 가져갈 수 있도록 결정했습니다. 그래서 디스크에 큰 부동 소수점 벡터를 유지하려고 하지 않고 삽입 시 정규화, 양자화를 할 수 있었습니다. 많은 벡터 DB는 사용자가 입력한 내용(전체 정밀도 벡터)을 기억한다는 사실을 희생하지 않습니다.</p>\n    <p>이것들은 단지 두 가지 임의의 예일 뿐이지만, 저는 이 아이디어를 모든 곳에 적용합니다. 이제 문제는 물론 올바른 것을 희생해야 한다는 것입니다. 종종 매우 큰 복잡성을 차지하는 5%의 기능이 있는데, 그것이 바로 없애야 할 좋은 것입니다 :D</p>\n</details>\n"
  },
  {
    "path": "README.md",
    "content": "# Cognitive load is what matters\n\n[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)\n\n*It is a living document, last update: **October 2025.** Your contributions are welcome!*\n\n## Introduction\nThere 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.\n\nSometimes 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.  \n\nSince 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. \n\n## Cognitive load\n> Cognitive load is how much a developer needs to think in order to complete a task.\n\nWhen 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.\n\n*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.***\n\n<div align=\"center\">\n  <img src=\"/img/cognitiveloadv6.png\" alt=\"Cognitive load\" width=\"750\">\n</div>\n\nWe should reduce the cognitive load in our projects as much as possible.\n\n<details>\n  <summary><b>Cognitive load and interruptions</b></summary>\n  <div align=\"center\">\n    <img src=\"img/interruption.jpeg\" width=\"480\">\n  </div>\n</details>\n\n> 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.\n\n## Types of cognitive load\n**Intrinsic** - caused by the inherent difficulty of a task. It can't be reduced, it's at the very heart of software development.  \n\n**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. \n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Intrinsic vs Extraneous\" width=\"600\">\n</div>\n\nLet's jump straight to the concrete practical examples of extraneous cognitive load.\n\n---\n\nWe will refer to the level of cognitive load as follows:  \n`🧠`: fresh working memory, zero cognitive load  \n`🧠++`: two facts in our working memory, cognitive load increased  \n`🤯`: cognitive overload, more than 4 facts  \n\n> Our brain is much more complex and unexplored, but we can go with this simplistic model.\n\n## Complex conditionals \n```go\nif val > someConstant // 🧠+\n    && (condition2 || condition3) // 🧠+++, prev cond should be true, one of c2 or c3 has to be true\n    && (condition4 && !condition5) { // 🤯, we are messed up by this point\n    ...\n}\n```\n\nIntroduce intermediate variables with meaningful names:\n```go\nisValid = val > someConstant\nisAllowed = condition2 || condition3\nisSecure = condition4 && !condition5 \n// 🧠, we don't need to remember the conditions, there are descriptive variables\nif isValid && isAllowed && isSecure {\n    ...\n}\n```\n\n## Nested ifs\n```go\nif isValid { // 🧠+, okay nested code applies to valid input only\n    if isSecure { // 🧠++, we do stuff for valid and secure input only\n        stuff // 🧠+++\n    }\n} \n```\n\nCompare it with the early returns:\n```go\nif !isValid\n    return\n \nif !isSecure\n    return\n\n// 🧠, we don't really care about earlier returns, if we are here then all good\n\nstuff // 🧠+\n```\n\nWe can focus on the happy path only, thus freeing our working memory from all sorts of preconditions.\n\n## Inheritance nightmare\nWe are asked to change a few things for our admin users: `🧠`\n\n`AdminController extends UserController extends GuestController extends BaseController`\n\nOhh, part of the functionality is in `BaseController`, let's have a look: `🧠+`  \nBasic role mechanics got introduced in `GuestController`: `🧠++`  \nThings got partially altered in `UserController`: `🧠+++`  \nFinally we are here, `AdminController`, let's code stuff! `🧠++++`  \n\nOh, 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: `🤯`\n\nPrefer composition over inheritance. We won't go into detail - there's [plenty of material](https://www.youtube.com/watch?v=hxGOiiR9ZKg) out there.\n\n## Too many small methods, classes or modules\n> Method, class and module are interchangeable in this context.\n \nMantras like \"methods should be shorter than 15 lines of code\" or \"classes should be small\" turned out to be somewhat wrong.\n\n**Deep module** - simple interface, complex functionality  \n**Shallow module** - interface is relatively complex compared to the small functionality it provides \n\n<div align=\"center\">\n  <img src=\"/img/deepmodulev8.png\" alt=\"Deep module\" width=\"700\">\n</div>\n\nHaving 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.  \n\n> Information hiding is paramount, and we don't hide as much complexity in shallow modules.\n\nI 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.\n\nOnce 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.  \n\n> The best components are those that provide powerful functionality yet have a simple interface.  \n> \n> *John Ousterhout, A Philosophy of Software Design*\n\nThe interface of the Unix I/O is very simple. It has only five basic calls:\n```python\nopen(path, flags, permissions)\nread(fd, buffer, count)\nwrite(fd, buffer, count)\nlseek(fd, offset, referencePosition)\nclose(fd)\n```\n\nA 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.\n\n> 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).\n\n<details>\n    <summary><b>Important things should be big, examples</b></summary>\n    <br>\n    <div align=\"center\">\n        <img src=\"/img/dirty.png\" alt=\"Clean vs Dirty\" width=\"600\">\n    </div>\n    <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>\n    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.\n</details>\n\nP.S. If you think we are rooting for bloated God objects with too many responsibilities, you got it wrong.\n\n## Responsible for one thing\nAll 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.  \n\nWe make changes to our systems to satisfy our users and stakeholders. We are responsible to them.  \n\n> A module should be responsible to one, and only one, user or stakeholder.  \n\nThis 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.    \n\nBut 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.  \n\n## Too many shallow microservices\nThis 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.\n\nI 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. `🤯`  \n\nIs 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.*\n\nThe [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.\n\nA 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.\n\n## Feature-rich languages\nWe feel excited when new features got released in our favourite language. We spend some time learning these features, we build code upon them.\n\nIf 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!**\n \n**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.** `🤯`\n\nThese statements are made by none other than Rob Pike.\n\n> Reduce cognitive load by limiting the number of choices.  \n\nLanguage features are OK, as long as they are orthogonal to each other.\n\n<details>\n  <summary><b>Thoughts from an engineer with 20 years of C++ experience ⭐️</b></summary>\n  <br>\n  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>\n  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>\n  Like, can you imagine, the token <code>||</code> has a different meaning in <code>requires ((!P&lt;T&gt; || !Q&lt;T&gt;))</code> and in <code>requires (!(P&lt;T&gt; || Q&lt;T&gt;))</code>. The first is the constraint disjunction, the second is the good-old logical OR operator, and they behave differently.<br><br>\n  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>\n  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>\n  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>\n  <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>\n  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>\n  <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>\n  <p>Thanks to <a href=\"https://0xd34df00d.me\" target=\"_blank\">0xd34df00d</a> for writing.</p>\n</details>\n\n## Business logic and HTTP status codes\nOn the backend we return:  \n`401` for expired JWT token  \n`403` for not enough access  \n`418` for banned users  \n\nThe engineers on the frontend use backend API to implement login functionality. They would have to temporarily create the following cognitive load in their brains:  \n`401` is for expired JWT token // `🧠+`, ok just temporarily remember it  \n`403` is for not enough access // `🧠++`  \n`418` is for banned users // `🧠+++`  \n\nFrontend 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.\n\nThen QA engineers come into play:\n\"Hey, I got `403` status, is that expired token or not enough access?\"\n**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.**\n\nWhy 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:\n```json\n{\n    \"code\": \"jwt_has_expired\"\n}\n```\n\nCognitive load on the frontend side: `🧠` (fresh, no facts are held in mind)  \nCognitive load on the QA side: `🧠`\n\nThe 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.  \n\n> 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. \n\nP.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.\n\n## Abusing DRY principle\n\nDo 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.\n\nNowadays, 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. `🤯`  \n\nIn 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.  \n\nRob Pike once said:\n\n> A little copying is better than a little dependency.  \n\nWe 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.  \n\n**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.  \n\n## Tight coupling with a framework\nThere'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.\n\nWorse 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. `🤯`\n\n**By no means do we advocate to invent everything from scratch!**\n\nWe 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.  \n\n> [Why I Hate Frameworks](https://minds.md/benji/frameworks)\n\n## Layered architecture\nThere is a certain engineering excitement about all this stuff.\n\nI 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. `🤯`\n\n**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. `🤯`  \n\n<div align=\"center\">\n  <img src=\"/img/layers.png\" alt=\"Layers\" width=\"400\">\n</div>\n\nThis 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.** \n\nIf 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).  \n\n> With a sufficient number of users of an API,  \n> it does not matter what you promise in the contract:  \n> all observable behaviours of your system  \n> will be depended on by somebody.\n\nWe 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.  \n\n**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.  \n\nThese 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).\n\nDo 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.\n\n**[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.**\n\n## Domain-driven design\nDomain-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.\n\nUbiquitous 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. \n\nChances 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. `🤯`  \n\nTeam 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.  \n\n## Cognitive load in familiar projects\n\n> 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!  \n>\n> 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.  \n>\n> 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!”  \n>\n> 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.  \n>\n> *Thanks to [Dan North](https://dannorth.net) for his comment*.  \n\nIf you've internalized the mental models of the project into your long-term memory, you won't experience a high cognitive load.  \n\n<div align=\"center\">\n  <img src=\"/img/mentalmodelsv15.png\" alt=\"Mental models\" width=\"700\">\n</div>\n\nThe 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.  \n\n> 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.**\n\nOnce 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.  \n\n## Examples\n> Software systems are perhaps the most intricate and complex (in terms of number of distinct kinds of parts) of the things humanity makes.\n>\n> *Fred Brooks, The Mythical Man-Month*\n\n\n- Our architecture is a standard CRUD app architecture, [a Python monolith on top of Postgres](https://danluu.com/simple-architectures/)\n- How Instagram scaled to 14 million users with [only 3 engineers](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)\n- 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\n- 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)\n- Designing for Understandability: [The Raft Consensus Algorithm](https://www.youtube.com/watch?v=vYp4LYbnnW8)\n\nThese architectures are quite boring and easy to understand. Anyone can grasp them without much mental effort.  \n\n<details>\n    <summary><b>Coding principles and experience</b></summary>\n    <div align=\"center\">\n        <img src=\"img/complexity.png\" alt=\"Super simple code\" width=\"500\">\n    </div>\n    <a href=\"https://twitter.com/flaviocopes\">@flaviocopes</a>\n</details>\n\nInvolve junior developers in architecture reviews, they will help you to identify the mentally demanding areas.\n\n**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.  \n\n> 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.  \n>\n> *Brian Kernighan*\n\nIn 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:\n- 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?\n- Can we make changes quickly, or are there a lot of unknown unknowns, and people are afraid to touch things?\n- Can new people add features quickly? Are there some unique mental models to learn?\n\nThese 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.\n\n## Conclusion\nImagine 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. `🤯`  \n\nDo 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.**\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Smart author\" width=\"600\">\n</div>\n\nWe should reduce any cognitive load above and beyond what is intrinsic to the work we do. \n\n---\n[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\n\n<details>\n    <summary><b>Comments (6)</b></summary>\n    <br>\n    <p><strong>Rob Pike</strong> <i>(Unix, Golang)</i><br>Nice article.</p>\n    <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>\n    <p><strong><a href=\"https://x.com/elonmusk/status/1872346903792566655\" target=\"_blank\">Elon Musk</a></strong> <i>(Rockets)</i><br>True.</p>\n    <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>\n    <p>The irony? Many of these complexity-inducing patterns were implemented in the name of \"clean code.\"</p>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <p>Sometimes the simplest solution is the best one, even in a complex system.</p>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n</details>\n"
  },
  {
    "path": "README.np.md",
    "content": "# संज्ञानात्मक भार नै महत्त्वपूर्ण छ\n\n[प्रोम्प्ट](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)\n\n*यो एक जीवित दस्तावेज हो, अन्तिम अपडेट: **अक्टोबर २०२५।** तपाईंको योगदान स्वागत छ!*\n\n## परिचय\nत्यहाँ धेरै buzzwords र उत्तम अभ्यासहरू छन्, तर तिनीहरूमध्ये धेरै असफल भएका छन्। तिनीहरू असफल भए किनभने तिनीहरू कल्पना गरिएका थिए, वास्तविक होइनन्। यी विचारहरू सौन्दर्य र व्यक्तिपरक निर्णयहरूमा आधारित थिए। हामीलाई केही अधिक आधारभूत चाहिन्छ, केही जुन गलत हुन सक्दैन।\n\nकहिलेकाहीं हामी कोड मार्फत जाँदा भ्रम महसुस गर्छौं। भ्रमले समय र पैसा खर्च गर्छ। भ्रम उच्च *संज्ञानात्मक भार* को कारणले गर्दा हुन्छ। यो केही फ्यान्सी अमूर्त अवधारणा होइन, बरु **एक आधारभूत मानव बाधा हो।** यो कल्पना गरिएको होइन, यो त्यहाँ छ र हामी यसलाई महसुस गर्न सक्छौं।\n\nहामी कोड लेख्नु भन्दा धेरै समय पढ्न र बुझ्न खर्च गर्छौं, त्यसैले हामीले निरन्तर आफैलाई सोध्नुपर्छ कि हामी हाम्रो कोडमा अत्यधिक संज्ञानात्मक भार इम्बेड गर्दैछौं कि छैनौं।\n\n## संज्ञानात्मक भार\n> संज्ञानात्मक भार भनेको कुनै कार्य पूरा गर्न विकासकर्ताले कति सोच्नु पर्छ।\n\nकोड पढ्दा, तपाईंले चरहरूको मानहरू, नियन्त्रण प्रवाह तर्क र कल अनुक्रमहरू जस्ता चीजहरू आफ्नो टाउकोमा राख्नुहुन्छ। औसत व्यक्तिले कार्य सम्झनामा लगभग [चार यस्ता खण्डहरू](https://github.com/zakirullin/cognitive-load/issues/16) राख्न सक्छ। एक पटक संज्ञानात्मक भार यो सीमामा पुग्छ, चीजहरू बुझ्न धेरै गाह्रो हुन्छ।\n\n*मानौं हामीलाई पूर्ण रूपमा अपरिचित परियोजनामा केही समाधानहरू गर्न भनिएको छ। हामीलाई भनिएको थियो कि वास्तवमा स्मार्ट विकासकर्ताले यसमा योगदान गरेको थियो। धेरै राम्रो architectures, फ्यान्सी पुस्तकालयहरू र ट्रेंडी प्रविधिहरू प्रयोग गरियो। अर्को शब्दमा, **लेखकले हाम्रो लागि उच्च संज्ञानात्मक भार सिर्जना गरेको थियो।***\n\n<div align=\"center\">\n  <img src=\"/img/cognitiveloadv6.png\" alt=\"Cognitive load\" width=\"750\">\n</div>\n\nहामीले हाम्रो परियोजनाहरूमा संज्ञानात्मक भार सकेसम्म कम गर्नुपर्छ।\n\n<details>\n  <summary><b>संज्ञानात्मक भार र अवरोधहरू</b></summary>\n  <div align=\"center\">\n    <img src=\"img/interruption.jpeg\" width=\"480\">\n  </div>\n</details>\n\n> हामी \"संज्ञानात्मक भार\" लाई अनौपचारिक अर्थमा प्रयोग गर्न जाँदैछौं; कहिलेकाहीं यो संज्ञानात्मक भार को विशिष्ट वैज्ञानिक अवधारणा संग मेल खान्छ, तर हामी यो कहाँ मेल खान्छ र कहाँ खाँदैन भन्ने बारेमा पर्याप्त जान्दैनौं।\n\n## संज्ञानात्मक भारका प्रकारहरू\n**आन्तरिक** - कार्यको निहित कठिनाइले गर्दा हुन्छ। यसलाई कम गर्न सकिँदैन, यो सफ्टवेयर विकासको मुटुमा छ।\n\n**बाह्य** - जानकारी प्रस्तुत गरिएको तरिकाले सिर्जना गरिएको। कार्यसँग सीधा सम्बन्धित नभएका कारकहरूले गर्दा हुन्छ, जस्तै स्मार्ट लेखकको विचित्रता। धेरै कम गर्न सकिन्छ। हामी यस प्रकारको संज्ञानात्मक भारमा ध्यान केन्द्रित गर्नेछौं।\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Intrinsic vs Extraneous\" width=\"600\">\n</div>\n\nआउनुहोस् सीधा बाह्य संज्ञानात्मक भारको ठोस व्यावहारिक उदाहरणहरूमा जाऔं।\n\n---\n\nहामी संज्ञानात्मक भारको स्तरलाई निम्नानुसार सन्दर्भ गर्नेछौं:\n`🧠`: ताजा कार्य सम्झना, शून्य संज्ञानात्मक भार\n`🧠++`: हाम्रो कार्य सम्झनामा दुई तथ्यहरू, संज्ञानात्मक भार बढ्यो\n`🤯`: संज्ञानात्मक ओभरलोड, ४ भन्दा बढी तथ्यहरू\n\n> हाम्रो मस्तिष्क धेरै जटिल र अन्वेषण नगरिएको छ, तर हामी यो सरल मोडेलसँग जान सक्छौं।\n\n## जटिल सर्तहरू\n```go\nif val > someConstant // 🧠+\n    && (condition2 || condition3) // 🧠+++, अघिल्लो सर्त सत्य हुनुपर्छ, c2 वा c3 मध्ये एक सत्य हुनुपर्छ\n    && (condition4 && !condition5) { // 🤯, यो बिन्दुमा हामी गडबड छौं\n    ...\n}\n```\n\nअर्थपूर्ण नामहरू भएका मध्यवर्ती चरहरू परिचय गर्नुहोस्:\n```go\nisValid = val > someConstant\nisAllowed = condition2 || condition3\nisSecure = condition4 && !condition5 \n// 🧠, हामीले सर्तहरू सम्झनु पर्दैन, वर्णनात्मक चरहरू छन्\nif isValid && isAllowed && isSecure {\n    ...\n}\n```\n\n## नेस्टेड ifs\n```go\nif isValid { // 🧠+, ठीक छ नेस्टेड कोड मान्य इनपुटमा मात्र लागू हुन्छ\n    if isSecure { // 🧠++, हामी मान्य र सुरक्षित इनपुटको लागि मात्र काम गर्छौं\n        stuff // 🧠+++\n    }\n} \n```\n\nयसलाई प्रारम्भिक रिटर्न्ससँग तुलना गर्नुहोस्:\n```go\nif !isValid\n    return\n \nif !isSecure\n    return\n\n// 🧠, हामी पहिलेको रिटर्न्सको बारेमा वास्तवमा वास्ता गर्दैनौं, यदि हामी यहाँ छौं भने सबै राम्रो छ\n\nstuff // 🧠+\n```\n\nहामी केवल खुसी मार्गमा ध्यान केन्द्रित गर्न सक्छौं, यसरी हाम्रो कार्य सम्झनालाई सबै प्रकारका पूर्व-शर्तहरूबाट मुक्त गर्दै।\n\n## इन्हेरिटेन्स दुःस्वप्न\nहामीलाई हाम्रो प्रशासक प्रयोगकर्ताहरूको लागि केही चीजहरू परिवर्तन गर्न भनिएको छ: `🧠`\n\n`AdminController extends UserController extends GuestController extends BaseController`\n\nओह, कार्यक्षमताको केही भाग `BaseController` मा छ, हेरौं: `🧠+`\nआधारभूत भूमिका मेकानिक्स `GuestController` मा प्रस्तुत गरियो: `🧠++`\nचीजहरू आंशिक रूपमा `UserController` मा परिवर्तन गरियो: `🧠+++`\nअन्तमा हामी यहाँ छौं, `AdminController`, कोड गरौं! `🧠++++`\n\nओह, पर्खनुहोस्, त्यहाँ `SuperuserController` छ जसले `AdminController` लाई विस्तार गर्छ। `AdminController` परिमार्जन गरेर हामी इन्हेरिट गरिएको क्लासमा चीजहरू तोड्न सक्छौं, त्यसैले पहिले `SuperuserController` मा गोता लगाऔं: `🤯`\n\nइन्हेरिटेन्स भन्दा कम्पोजिसनलाई प्राथमिकता दिनुहोस्। हामी विस्तृत विवरणमा जाँदैनौं - त्यहाँ [धेरै सामग्री](https://www.youtube.com/watch?v=hxGOiiR9ZKg) छ।\n\n## धेरै साना मेथडहरू, क्लासहरू वा मोड्युलहरू\n> मेथड, क्लास र मोड्युल यो सन्दर्भमा आदान-प्रदान गर्न मिल्छन्।\n\n\"मेथडहरू १५ लाइन कोड भन्दा छोटो हुनुपर्छ\" वा \"क्लासहरू सानो हुनुपर्छ\" जस्ता मन्त्रहरू केही हदसम्म गलत साबित भए।\n\n**गहिरो मोड्युल** - सरल इन्टरफेस, जटिल कार्यक्षमता\n**उथली मोड्युल** - इन्टरफेस यसले प्रदान गर्ने सानो कार्यक्षमताको तुलनामा अपेक्षाकृत जटिल छ\n\n<div align=\"center\">\n  <img src=\"/img/deepmodulev8.png\" alt=\"Deep module\" width=\"700\">\n</div>\n\nधेरै उथला मोड्युलहरू हुनुले परियोजना बुझ्न गाह्रो बनाउन सक्छ। **हामीले प्रत्येक मोड्युलको जिम्मेवारीहरू मात्र होइन, तर तिनीहरूको सबै अन्तरक्रियाहरू पनि दिमागमा राख्नुपर्छ।** उथले मोड्युलको उद्देश्य बुझ्न, हामीले पहिले सबै सम्बन्धित मोड्युलहरूको कार्यक्षमता हेर्नुपर्छ। यस्ता उथले घटकहरू बीच उफ्रनु मानसिक रूपमा थकाइ लाग्दो छ, <a target=\"_blank\" href=\"https://blog.separateconcerns.com/2023-09-11-linear-code.html\">रैखिक सोच</a> हामी मानिसहरूको लागि अधिक प्राकृतिक छ।\n\n> जानकारी लुकाउनु सर्वोपरि छ, र हामी उथले मोड्युलहरूमा धेरै जटिलता लुकाउँदैनौं।\n\nमसँग दुई पेट परियोजनाहरू छन्, दुवै लगभग ५K लाइन कोड छन्। पहिलो एकमा ८० उथला क्लासहरू छन्, जबकि दोस्रोमा केवल ७ गहिरो क्लासहरू छन्। मैले यी मध्ये कुनै पनि परियोजना डेढ वर्षसम्म कायम राखेको छैन।\n\nएक पटक म फर्किएँ, मैले महसूस गरें कि पहिलो परियोजनामा ती ८० क्लासहरू बीचको सबै अन्तरक्रियाहरू खोल्न अत्यन्तै गाह्रो थियो। म कोडिङ सुरु गर्नु अघि मैले ठूलो मात्रामा संज्ञानात्मक भार पुनर्निर्माण गर्नुपर्ने थियो। अर्कोतर्फ, म दोस्रो परियोजना छिटो बुझ्न सकें, किनभने यसमा केवल केही गहिरो क्लासहरू सरल इन्टरफेससहित थिए।\n\n> उत्तम घटकहरू ती हुन् जसले शक्तिशाली कार्यक्षमता प्रदान गर्छन् तर सरल इन्टरफेस छ।\n> \n> *John Ousterhout, A Philosophy of Software Design*\n\nUnix I/O को इन्टरफेस धेरै सरल छ। यसमा केवल पाँच आधारभूत कलहरू छन्:\n```python\nopen(path, flags, permissions)\nread(fd, buffer, count)\nwrite(fd, buffer, count)\nlseek(fd, offset, referencePosition)\nclose(fd)\n```\n\nयस इन्टरफेसको आधुनिक कार्यान्वयनमा **लाखौं लाइन कोड छ।** धेरै जटिलता हुड मुनि लुकेको छ। तर यसको सरल इन्टरफेसको कारण प्रयोग गर्न सजिलो छ।\n\n> यो गहिरो मोड्युल उदाहरण 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)।\n\n<details>\n    <summary><b>महत्त्वपूर्ण चीजहरू ठूला हुनुपर्छ, उदाहरणहरू</b></summary>\n    <br>\n    <div align=\"center\">\n        <img src=\"/img/dirty.png\" alt=\"Clean vs Dirty\" width=\"600\">\n    </div>\n    <blockquote>यदि तपाईंले आफ्नो महत्त्वपूर्ण \"क्रक्स\" फङ्क्सनहरूलाई ठूलो (\"फोहोर\") हुन दिनुभयो भने, तिनीहरूलाई फङ्क्सनको समुद्रबाट छान्न सजिलो हुन्छ, तिनीहरू स्पष्ट रूपमा महत्त्वपूर्ण छन्: केवल तिनीहरूलाई हेर्नुहोस्, तिनीहरू ठूला छन्!</blockquote>\n    यो तस्बिर 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> फेला पार्नुहुनेछ।\n</details>\n\nP.S. यदि तपाईंलाई लाग्छ कि हामी धेरै जिम्मेवारीहरू भएका फुलाएको God objects को लागि समर्थन गर्दैछौं, तपाईंले यसलाई गलत बुझ्नुभयो।\n\n## एउटा कुराको लागि जिम्मेवार\nप्रायः, हामी धेरै उथला मोड्युलहरू सिर्जना गर्छौं, केही अस्पष्ट \"एक मोड्युल एक, र केवल एक, कुराको लागि जिम्मेवार हुनुपर्छ\" सिद्धान्त पछ्याउँदै। यो धमिलो एक कुरा के हो? एक वस्तु तत्काल बनाउनु एक कुरा हो, होइन? त्यसोभए [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks) ठीक जस्तो देखिन्छ। **यस्ता क्लासहरूको नाम र इन्टरफेसहरू तिनीहरूको सम्पूर्ण कार्यान्वयन भन्दा मानसिक रूपमा बढी कर लाग्दछ, त्यो कस्तो प्रकारको अमूर्तता हो?** केही गलत भयो।\n\nहामी हाम्रा प्रयोगकर्ताहरू र सरोकारवालाहरूलाई सन्तुष्ट पार्न हाम्रो प्रणालीमा परिवर्तनहरू गर्छौं। हामी तिनीहरूप्रति जिम्मेवार छौं।\n\n> एक मोड्युल एक, र केवल एक, प्रयोगकर्ता वा सरोकारवालाप्रति जिम्मेवार हुनुपर्छ।\n\nयो Single Responsibility Principle को बारेमा हो। सरल शब्दमा, यदि हामीले एक ठाउँमा बग परिचय गर्छौं, र त्यसपछि दुई फरक व्यापार व्यक्तिहरू गुनासो गर्न आउँछन्, हामीले सिद्धान्तको उल्लंघन गरेका छौं। यसको हाम्रो मोड्युलमा गर्ने चीजहरूको संख्यासँग कुनै सरोकार छैन।\n\nतर अहिले पनि, यो नियमले राम्रो भन्दा बढी हानि गर्न सक्छ। यो सिद्धान्तलाई व्यक्तिहरू जति छन् त्यति फरक तरिकाले बुझ्न सकिन्छ। राम्रो दृष्टिकोण यो सबैले कति संज्ञानात्मक भार सिर्जना गर्छ हेर्नु हुनेछ। एक ठाउँमा परिवर्तनले विभिन्न व्यापार धाराहरूमा प्रतिक्रियाहरूको श्रृंखला ट्रिगर गर्न सक्छ भनेर सम्झनु मानसिक रूपमा माग गर्ने छ। र त्यो यति हो, सिक्न कुनै फ्यान्सी शब्दहरू छैनन्।\n\n## धेरै उथला माइक्रोसर्भिसहरू\nयो उथले-गहिरो मोड्युल सिद्धान्त स्केल-अग्नोस्टिक हो, र हामी यसलाई माइक्रोसर्भिस आर्किटेक्चरमा लागू गर्न सक्छौं। धेरै उथला माइक्रोसर्भिसहरूले कुनै राम्रो गर्दैनन् - उद्योग केही हदसम्म \"म्याक्रोसर्भिस\" तिर अगाडि बढिरहेको छ, अर्थात्, सेवाहरू जुन त्यति उथले छैनन् (=गहिरो)। सबैभन्दा खराब र समाधान गर्न गाह्रो घटनाहरू मध्ये एक तथाकथित वितरित मोनोलिथ हो, जुन प्रायः यो अत्यधिक दानेदार उथले विभाजनको परिणाम हो।\n\nमैले एक पटक एक स्टार्टअपमा परामर्श गरें जहाँ पाँच विकासकर्ताहरूको टोलीले १७(!) माइक्रोसर्भिसहरू प्रस्तुत गरे। तिनीहरू १० महिना पछाडि थिए र सार्वजनिक रिलीजको नजिक कतै पनि देखिँदैनथे। प्रत्येक नयाँ आवश्यकताले ४+ माइक्रोसर्भिसहरूमा परिवर्तनहरू निम्त्यायो। यस्तो वितरित प्रणालीमा समस्या पुन: उत्पादन र डिबग गर्न ठूलो समय लाग्यो। बजार र संज्ञानात्मक भार दुवै समय अस्वीकार्य रूपमा उच्च थिए। `🤯`\n\nके यो नयाँ प्रणालीको अनिश्चितता सम्बोधन गर्ने सही तरिका हो? सुरुमा सही तार्किक सीमाहरू प्राप्त गर्न अत्यन्तै गाह्रो छ। कुञ्जी भनेको तपाईंले जिम्मेवारीपूर्वक पर्खन सक्ने गरी ढिलो निर्णयहरू गर्नु हो, किनभने त्यो तपाईंसँग सबैभन्दा बढी जानकारी हुने समय हो। अगाडि नेटवर्क तह प्रस्तुत गरेर, हामी हाम्रो डिजाइन निर्णयहरू सुरुदेखि नै उल्टाउन गाह्रो बनाउँछौं। टोलीको एक मात्र औचित्य थियो: \"FAANG कम्पनीहरूले माइक्रोसर्भिस आर्किटेक्चर प्रभावकारी साबित गरे\"। *नमस्ते, तपाईंले ठूलो सपना देख्न बन्द गर्नुपर्छ।*\n\n[Tanenbaum-Torvalds debate](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate) ले तर्क गर्‍यो कि Linux को मोनोलिथिक डिजाइन त्रुटिपूर्ण र अप्रचलित थियो, र यसको सट्टा माइक्रोकर्नेल आर्किटेक्चर प्रयोग गर्नुपर्छ। वास्तवमा, माइक्रोकर्नेल डिजाइन \"सैद्धान्तिक र सौन्दर्य\" दृष्टिकोणबाट श्रेष्ठ जस्तो देखिन्थ्यो। व्यावहारिक पक्षम\n"
  },
  {
    "path": "README.prompt.md",
    "content": "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.\n\nHere's an example that's hard for people to understand:\n```\nif val > someConstant // (one fact in human memory)\n    && (condition2 || condition3) // (three facts in human memory), prev cond should be true, one of c2 or c3 has be true\n    && (condition4 && !condition5) { // (human memory overload), we are messed up by this point\n    ...\n}\n```\n\nA good example, introducing intermediate variables with meaningful names:\n```\nisValid = val > someConstant\nisAllowed = condition2 || condition3\nisSecure = condition4 && !condition5 \n// (human working memory is clean), we don't need to remember the conditions, there are descriptive variables\nif isValid && isAllowed && isSecure {\n    ...\n}\n```\n\n- 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.\n- Make conditionals readable, extract complex expressions into intermediate variables with meaningful names.\n- Prefer early returns over nested ifs, free working memory by letting the reader focus only on the happy path only.\n- Prefer composition over deep inheritance, don’t force readers to chase behavior across multiple classes.\n- 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.\n- Prefer deep method/classes/modules (simple interface, complex functionality) over many shallow ones. \n- 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.\n- Use self-descriptive values, avoid custom mappings that require memorization.\n- Don’t abuse DRY, a little duplication is better than unnecessary dependencies.\n- 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.\n"
  },
  {
    "path": "README.pt-br.md",
    "content": "# Carga Cognitiva é o que importa\r\n\r\n[Prompt](README.prompt.md) | [Versão do Blog](https://minds.md/zakirullin/cognitive) | [Original](README.md)\r\n\r\n*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!*\r\n\r\n## Introdução\r\n\r\nExtistem 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.\r\n\r\nAlgumas 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.\r\n\r\nJá 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.\r\n\r\n## Carga Cognitiva\r\n> Carga cognitiva é o quanto um desenvolvedor precisa pensar para completar uma tarefa.\r\n\r\nQuando 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.\r\n\r\n*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.***\r\n\r\n<div align=\"center\">\r\n  <img src=\"/img/cognitiveloadv6.png\" alt=\"Cognitive load\" width=\"750\">\r\n</div>\r\n\r\nDeveríamos reduzir a carga cognitiva em nossos projetos o máximo possível.\r\n\r\n<details>\r\n  <summary><b>Carga cognitiva e interrupções</b></summary>\r\n  <img src=\"img/interruption.jpeg\"><br>\r\n</details>\r\n\r\n> 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.\r\n\r\n## Tipos de carga cognitiva\r\n**Intrínseca** — causada pela dificuldade inerente da tarefa. Não pode ser reduzido, já que se está na cerne do desenvolvimento do *software*.\r\n\r\n**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.\r\n\r\n<div align=\"center\">\r\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Intrínseca vs Extrínseca\" width=\"600\">\r\n</div>\r\n\r\nPularemos direto aos exemplos práticos e concretos de carga cognitiva extrínseca.\r\n\r\n---\r\n\r\nReferiremos ao nível de carga coginitiva como o seguinte:\r\n\r\n- `🧠`: memória de trabalho fresca, nenhuma carga coginitiva.\r\n- `🧠++`: dois fatos em nossa memória de trabalho, carga cognitiva aumentada.\r\n- `🤯`: sobrecarga cognitiva, mais de 4 fatos\r\n\r\n> Nosso cérebro é muito mais complexo e não explorado, mas podemos seguir neste modelo simplístico.\r\n\r\n## Condicionais complexas\r\n```go\r\nif val > someConstant // 🧠+\r\n    && (condition2 || condition3) // 🧠+++, condição prévia deve ser verdadeira, uma de c2 ou c3 tem de ser verdadeira\r\n    && (condition4 && !condition5) { // 🤯, estamos confusos neste momento\r\n    ...\r\n}\r\n```\r\n\r\nIntroduza variáveis intermediárias com nomes significativos:\r\n```go\r\nisValid = val > someConstant\r\nisAllowed = condition2 || condition3\r\nisSecure = condition4 && !condition5 \r\n// 🧠, não precisamos lembrar as condições, elas são variáveis descritivas\r\nif isValid && isAllowed && isSecure {\r\n    ...\r\n}\r\n```\r\n\r\n## Ifs aninhados\r\n```go\r\nif isValid { // 🧠+, ok, código aninhado se aplica a entradas válidas, apenas\r\n    if isSecure { // 🧠++, fazemos a tarefa apenas para entradas válidas e seguras \r\n        stuff // 🧠+++\r\n    }\r\n}\r\n```\r\n\r\nCompare agora com *early returns*:\r\n```go\r\nif !isValid\r\n    return\r\n \r\nif !isSecure\r\n    return\r\n\r\n// 🧠, não precisamos ligar por conta dos retornos breves. Se estamos aqui, então tudo certo\r\n\r\nstuff // 🧠+\r\n```\r\n\r\nPodemos apenas focar no caminho feliz, liberando nossa memória de trabalho de todos esses tipos de pré-condições.\r\n\r\n## Pesadelo de herança\r\n\r\nFomos pedidos para mudar algumas coisas para nossos usuários administradores: `🧠`\r\n\r\n`AdminController extends UserController extends GuestController extends BaseController`\r\n\r\nAhh, parte da functionalidade está em `BaseControler`, vamos dar uma olhada: `🧠+`\r\n\r\nO mecanismo básico foi introduzido em `GuestController`: `🧠++`.\r\n\r\nAlgumas coisas foram parcialmente alteradas em `UserController`: `🧠+++`.\r\n\r\nFinalmente, estamos aqui, `AdminController`, vamos programar nossa tarefa! `🧠++++`.\r\n\r\nEi, espera! Existe um `SuperuserController` que extende `AdminController`. Ao modificar `AdminController`, podemos quebrar partes da classe herdeira, vamos mergulhar em `SuperuserController` primeiro: `🤯`\r\n\r\nPrefira 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í.\r\n\r\n## Pequenos métodos, classes ou módulos demasiados\r\n\r\n> Método, classes e módulos são intercambiáveis neste contexto.\r\n\r\nMantras como \"métodos deveriam ser menores que 15 linhas de código\" ou \"classes deveriam ser pequenas\" se tornaram em algo errado.\r\n\r\n**Módulo profundo** — interface simples, funcionalidade complexa\r\n**Módulo raso** — interface relativamente complexa comparada à pequena funcionalidade que isso provê.\r\n\r\n<div align=\"center\">\r\n  <img src=\"/img/deepmodulev8.png\" alt=\"Módulo Profundo\" width=\"700\">\r\n</div>\r\n\r\nTer 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.\r\n\r\n> Ocultar informação é fundamental. Não precisamos ocultar tanto a complexidade em módulos rasos.\r\n\r\nEu 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.\r\n\r\nAo 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.\r\n\r\n> Os melhores componentes são aqueles que provém funcionalidades poderosas enquanto mantém interfaces simples.\r\n> \r\n> *John Ousterhout, Um Filósofo de Deisgn de Software*\r\n\r\nA interface do *UNIX I/O* é bastante simples. Ele tem apenas cinco básicas chamadas.\r\n\r\n```py\r\nopen(caminho, bandeiras, permissões)\r\nread(da, buffer, contagem)\r\nwrite(da, buffer, contagem)\r\nlseek(da, desvio, posiçãoDeReferencia)\r\nclose(da)\r\n```\r\n\r\nUma 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.\r\n\r\n> 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)*.\r\n\r\n<details>\r\n    <summary><b>Coisas importantes deviam ser grandes, exemplos</b></summary>\r\n    <br>\r\n    <div align=\"center\">\r\n        <img src=\"/img/dirty.png\" alt=\"Clean vs Dirty\" width=\"600\">\r\n    </div>\r\n    <blockquote>\r\n      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!\r\n    </blockquote>\r\n\r\n  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í.\r\n</details>\r\n\r\nP.S. Caso pense que estamos enraizando objetos divinos inchados com muitas responsabilidades, você compreendeu errado.\r\n\r\n## Responsável por uma única coisa\r\n\r\nFrequentemente, 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.\r\n\r\nFazemos mudanças em nossos sistemas para satisfazer nossos usuários e *Stakeholders*. Somos responsáveis por eles.\r\n\r\n> Um módulo deveria ser responsável por um, e apenas um, usuário ou *Stakeholder*.\r\n\r\nIsso é 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.\r\n\r\nMas 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.\r\n\r\n## Muitos microsserviços rasos\r\n\r\nEsse 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.\r\n\r\nCerta 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. `🤯`\r\n\r\nEsta é 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.*\r\n\r\nO [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.\r\n\r\nUm 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.\r\n\r\n## Linguagem rica de recursos\r\n\r\nFicamos 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.\r\n\r\nSe 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!**\r\n\r\n**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**. `🤯`\r\n\r\nEssas afirmações foram feitas por ninguém menos que Rob Pike.\r\n\r\n> Reduza a carga cognitiva pelo número de escolhas.\r\n\r\nRecursos de linguagem são OK, contanto que sejam ortoginais entre si.\r\n\r\n<details>\r\n  <summary><b>Pensamentos de um engenheiro com 20 anos de experiência em C++ ⭐️</b></summary>\r\n  <br>\r\n  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!\r\n  <br><br>\r\n  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.\r\n  <br><br>\r\n  Tipo, você pode imaginar, o token <code>||</code> tem significado distinto em <code>requires ((!P&lt;T&gt; || !Q&lt;T&gt;))</code> e em <code>requires (!(P&lt;T&gt; || Q&lt;T&gt;))</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>\r\n  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>\r\n  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>\r\n  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>\r\n  <b>\r\n  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>\r\n  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>\r\n  <b>De nenhuma forma estou tentando julgar C++.</b> Amo a linguagem. Mas estou apenas cansado por agora.<br><br>\r\n  <p>Obrigado ao <a href=\"https://0xd34df00d.me\" target=\"_blank\">0xd34df00d</a> por escrever.</p>\r\n</details>\r\n\r\n## Lógica de negócios e código de status HTTP\r\n\r\nNo backend, retornamos:\r\n- `401` para tokens JWT expirados\r\n- `403` para acesso insuficiente\r\n- `418` para usuários banidos\r\n\r\nOs 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:\r\n- `401` para tokens JWT expirados: `🧠+`, ok  vamos temporariamente lembrar disso\r\n- `403` para acesso insuficiente: `🧠++`\r\n- `418` para usuários banidos: `🧠+++`\r\n\r\nDesenvolvedores 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.\r\n\r\nEntão os engenheiros QA entram na jogada:\r\n\"Ei, eu tenho um status `403`, isso seria o token expirado ou acesso insuficiente?\"\r\n**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**.\r\n\r\nPor 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:\r\n\r\n```json\r\n{\r\n  \"código\": \"jwt-expirado\"\r\n}\r\n```\r\n\r\nCarga cognitiva do lado do *front-end*: `🧠` (fresco, nenhum fato é guardado em mente).\r\nCarga cognitiva do lado do *QA*: `🧠`.\r\n\r\nAs 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.\r\n\r\n> 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.\r\n\r\nP.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.\r\n\r\n## Abusando do princípio DRY\r\n\r\nNã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.\r\n\r\nHoje 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. `🤯`\r\n\r\nDe 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.\r\n\r\nRob Pike disse, certa vez:\r\n\r\n> Uma pequena cópia é melhor que uma pequena dependência.\r\n\r\nSomos 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.\r\n\r\n**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.\r\n\r\n## Estreitamente acoplado a um *Framework*\r\n\r\nExistem 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.\r\n\r\nPior 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. `🤯`\r\n\r\n**De nenhuma forma eu advogo em pró de inventar tudo do zero!**\r\n\r\nPodemos 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.\r\n\r\n> [Why I Hate Frameworks](https://minds.md/benji/frameworks)\r\n\r\n## Arquitetura em camadas\r\n\r\nHá uma certa emoção de engenharia em tudo isso.\r\n\r\nEu 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. `🤯`\r\n\r\n**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. `🤯`\r\n\r\nEssa 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**.\r\n\r\n<details>\r\n  <summary><b>Princípios de programação e experiência</b></summary>\r\n  <img src=\"img/complexity.png\"><br>\r\n  <a href=\"https://twitter.com/flaviocopes\">@flaviocopes</a>\r\n</details>\r\n\r\nSe 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).\r\n\r\n> Com um número suficiente de usuários de uma API,\r\n> não importa qual a sua promessa no contrato:\r\n> todos os comportamentos de seu sistema\r\n> será dependente de alguém.\r\n\r\nFazemos 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.\r\n\r\n**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.\r\n\r\nEssas 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)\r\n\r\nNã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.\r\n\r\n**[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.**\r\n\r\n<div align=\"center\">\r\n  <img src=\"/img/layers.png\" alt=\"Camadas\" width=\"400\">\r\n</div>\r\n\r\n## *Domain-drive design*\r\n\r\n*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.\r\n\r\nLinguagem 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.\r\n\r\nChances 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. `🤯`\r\n\r\n*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.\r\n\r\n\r\n## Carga Cognitiva em projetos familiares\r\n\r\n> 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!\r\n>\r\n> É 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.\r\n>\r\n> 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!\"\r\n>\r\n> 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.\r\n>\r\n>*Obrigado ao [Dan North](https://dannorth.net) por seu comentário*.  \r\n\r\nSe tiver internalizado os modelos mentais do projeto em sua memória de longo-termo, não experienciará a carga cognitiva.\r\n\r\n<div align=\"center\">\r\n  <img src=\"/img/mentalmodelsv15.png\" alt=\"Modelos mentais\" width=\"700\">\r\n</div>\r\n\r\nQuanto mais modelos mentais tiver de aprender, mais tempo levará para um novo desenvolvedor entregar valor.\r\n\r\nUma 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.\r\n\r\nSe você mantiver a carga cognitiva baixa, pessoas podem contribuir para seu código-base dentro de algumas poucas horas ao entrar na empresa.\r\n\r\n## Exemplos\r\n\r\n- Nossa arquitetura é uma aplicação CRUD padrão, [Um monolito Python em cima do Postgres](https://danluu.com/simple-architectures/).\r\n- 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).\r\n- 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.\r\n- 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).\r\n\r\nEssas arquiteturas são bastante chatas e fáceis de compreender. Qualquer um pode compreendê-las sem muito esforço mental.\r\n\r\nEnvolva desenvolvedores júniors em suas reviews de arquitetura, eles podem te ajudar a identificar áreas mentalmente exaustivas.\r\n\r\n> 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.  \r\n> \r\n> *Fred Brooks, The Mythical Man-Month*\r\n\r\n**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.\r\n\r\n> 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.  \r\n>\r\n> *Brian Kernighan*\r\n\r\nEm 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:\r\n\r\n- É 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?\r\n- Podemos fazer alterações rapidamente ou há muitas incógnitas e as pessoas têm medo de mexer nas coisas?\r\n- Os novos funcionários podem adicionar recursos rapidamente? Há alguns modelos mentais únicos a serem aprendidos?\r\n\r\n> 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. \r\n\r\n**Carga cognitiva estranha que os outros têm de internalizar.**\r\n\r\nEssas 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.\r\n\r\n## Conclusão\r\n\r\nImagine 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.\r\n\r\nVocê 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.**\r\n\r\n<div align=\"center\">\r\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"O autor espeto\" width=\"600\">\r\n</div>\r\n\r\nDeveríamos reduzir qualquer carga cognitiva acima. É algo além de instrínseco para o trabalho que fazemos.\r\n\r\n---\r\n\r\n[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\r\n\r\n<details>\r\n    <summary><b>Comments</b></summary>\r\n    <br>\r\n    <p><strong>Rob Pike</strong> <i>(Unix, Golang)</i><br>Nice article.</p>\r\n    <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>\r\n    <p><strong><a href=\"https://x.com/elonmusk/status/1872346903792566655\" target=\"_blank\">Elon Musk</a></strong> <i>(Rockets)</i><br>True.</p>\r\n    <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>\r\n    <p>The irony? Many of these complexity-inducing patterns were implemented in the name of \"clean code.\"</p>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <p>Sometimes the simplest solution is the best one, even in a complex system.</p>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n    <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>\r\n</details>\r\n"
  },
  {
    "path": "README.tr.md",
    "content": "# Ö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, 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.\n\nBazen 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**.\n\nBu, ö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.\n\nÇü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:  \n\"**Yazdığımız koda gereğinden fazla bilişsel yük yüklüyor muyuz?**\" \n\n## Bilişsel yük\n\n> Bilişsel yük, bir geliştiricinin bir görevi tamamlamak için ne kadar düşünmesi gerektiğidir.\n\nKod 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**.\n\n*Diyelim ki bize tamamen yabancı bir projede bazı hataları düzeltmemiz söylendi. Projeye daha önce çok zeki bir geliştirici katkıda bulunmuş.  \nKulağa harika geliyor: karmaşık mimariler, havalı kütüphaneler ve trend teknolojiler kullanılmış.  \nYani başka bir deyişle, **yazarı bizim için oldukça yüksek bir bilişsel yük üretmiş**.*\n\n<div align=\"center\">\n  <img src=\"img/cognitiveloadv6.png\" alt=\"Cognitive load\" width=\"750\">\n</div>\n\n*Bu yüzden projelerimizde bilişsel yükü olabildğince azaltmalıyız.*\n\n<details>\n  <summary><b>Bilişsel yük ve bölünmeler</b></summary>\n  <img src=\"img/interruption.jpeg\"><br>\n</details>\n\n## Bilişsel yükün türleri\n\n**İç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.\n\n**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. \n\n<div align=\"center\">\n  <img src=\"img/smartauthorv14thanksmari.png\" alt=\"Intrinsic vs Extraneous\" width=\"600\">\n</div>\n\nŞimdi doğrudan dışsal bilişsel yükün somut pratik örneklerine geçelim.\n\n---\n\nBilişsel yük düzeyini şu şekilde ifade edeceğiz:  \n`🧠`: taze çalışma belleği, sıfır bilişsel yük  \n`🧠++`: çalışma hafızamızdaki iki yük, bilişsel yükün arttığını gösteriyor  \n`🤯`: Bilişsel aşırı yükleme, 4'ten fazla yük\n\n> Beynimiz daha karmaşık ve keşfedilmemiş, fakat biz bu basite indirgenmiş modelle devam edebiliriz.\n\n## Komplex koşullar\n\n```go\nif val > someConstant // 🧠+\n    && (condition2 || condition3) // 🧠+++, önceki koşul doğru olmalı, c2 veya c3'ten biri doğru olmalı\n    && (condition4 && !condition5) { // 🤯, bu noktada kafayı yedik\n    ...\n}\n```\n\nAnlamlı isimlere sahip ara değişkenleri tanıtın:\n\n```go\nisValid = val > someConstant\nisAllowed = condition2 || condition3\nisSecure = condition4 && !condition5 \n// 🧠, koşulları hatırlamamıza gerek yok, tanımlayıcı değişkenler var\nif isValid && isAllowed && isSecure {\n    ...\n}\n```\n\n## İç içe if döngüleri\n\n```go\nif isValid { // 🧠+, tamam, iç içe geçmiş kod yalnızca geçerli girdiye uygulanır\n    if isSecure { // 🧠++, yalnızca geçerli ve güvenli girdi zaman devam ediyoruz\n        stuff // 🧠+++\n    }\n} \n```\n\nBilişsel yükü bir de \"early return\" ile kıyaslayalım:\n\n```go\nif !isValid\n    return\n\nif !isSecure\n    return\n\n// 🧠, daha önceki getirileri pek umursamıyoruz, eğer buradaysak her şey yolunda demektir\n\nstuff // 🧠+\n```\n\nSadece “her şeyin yolunda gittiği senaryo”ya odaklanırsak, zihnimiz gereksiz ön koşullarla meşgul olmaz, daha rahat çalışırız.\n\n## Inheritance kabusu\n\nYönetici kullanıcılarımız için birkaç şeyi değiştirmemiz isteniyor: `🧠`\n\n`AdminController extends UserController extends GuestController extends BaseController`\n\nOhh, işin bir kısmı `BaseController`'da duruyor, bi' kontrol edelim: `🧠+`  \nTemel rol mekanikleri `GuestController`'da eklenmiş: `🧠++`  \n`UserController`'da bazı şeyler değiştirilmiş: `🧠+++`  \nVe sonunda, `AdminController`, artık kodlama zamanı! `🧠++++`  \n\nAma bir dakika, `AdminController` `SuperuserController`'dan extend edilmiş. `AdminController`’da bir değişiklik yaparsak, kalıtımla gelen bu sınıfta işler bozulabilir. Bu yüzden önce `SuperuserController`'a göz atmak lazım: `🤯`\n\nKalıtım (inheritance) yerine bileşimi (composition) tercih etmek en iyisi. Detaylara girmeyeceğiz ama internette bununla alakalı şöyle bir [video](https://www.youtube.com/watch?v=hxGOiiR9ZKg) var.\n\n## Çok sayıda küçük metot, sınıf ya da modül\n\n> Bu bağlamda metot, sınıf ve modül kelimeleri birbiriyle değiştirilebilir.\n\n\"Metotlar 15 satırı geçmemeli\" ya da \"sınıflar olabildiğince küçük olmalı\" gibi yazılım dogmaları, zamanla pek de doğru olmadığını gösterdi.\n\n**Deep module** - arayüzü basit, ama içinde karmaşık ve güçlü işler yapan yapı.  \n**Shallow module** - arayüzü nispeten karmaşık ama yaptığı iş aslında çok basit olan yapı \n\n<div align=\"center\">\n  <img src=\"img/deepmodulev8.png\" alt=\"Deep module\" width=\"700\">\n</div>\n\nÇok sayıda yüzeysel modül olması, projeyi anlamayı ciddi şekilde zorlaştırabilir. **Sadece her bir modülün ne iş yaptığını değil, aynı zamanda birbirleriyle nasıl etkileşime girdiklerini de aklımızda tutmamız gerekir.** Bir yüzeysel modülün ne işe yaradığını anlayabilmek için önce onunla bağlantılı tüm diğer modüllerin işlevine bakmak zorunda kalırız. `🤯`\n\n> Bilgiyi saklamak (information hiding) yazılımda çok kritik bir ilkedir; ama Shallow modüllerde bu karmaşıklığı yeterince gizleyemeyiz.\n\nBenim iki tane kişisel projem var, ikisi de yaklaşık 5.000 satırdan oluşuyor. İlk projede 80 tane shallow sınıf varken İkinci projede sadece 7 tane deep sınıf bulunuyor. Üzerlerinden 1,5 yıl geçmesine rağmen bu projelere hiç dokunmadığım bir süre zarfı oldu.\n\nAradan zaman geçip geri döndüğümdeyse şunu fark ettim: İlk projeye tekrar bakmak inanılmaz zordu. 80 sınıfın birbirine nasıl bağlandığını çözmek için adeta düğüm çözmeye çalışır gibi uğraşmam gerekiyordu. Kodu yazmaya başlamadan önce beynimde ciddi bir bilişsel yükü yeniden inşa etmem lazımdı. Ama ikinci projeye geçtiğimde işler çok daha kolaydı. Çünkü sadece birkaç tane sınıf vardı ve her biri sade, net bir arayüzle çalışıyordu. Ne ne işe yarıyor hemen kavradım, projeye rahatça adapte oldum.\n\n> En iyi bileşenler, güçlü bir işlevsellik sunmasına rağmen sade ve anlaşılır bir arayüze sahip olanlardır.  \n> **John K. Ousterhout**\n\nUNIX giriş/çıkış (I/O) sisteminin arayüzü oldukça sadedir. Sadece beş temel fonksiyonu vardır:\n\n```python\nopen(path, flags, permissions)\nread(fd, buffer, count)\nwrite(fd, buffer, count)\nlseek(fd, offset, referencePosition)\nclose(fd)\n```\n\nBu arayüzün modern bir uygulaması **yüz binlerce satır kod** içerebilir. Yani altında ciddi bir karmaşıklık yatar. Yine de, arayüzü sade olduğu için kullanımı oldukça kolaydır.\n\n> Bu derin modül örneği, John K. Ousterhout’un *[A Philosophy of Software Design](https://web.stanford.edu/~ouster/cgi-bin/book.php)* (Bir Yazılım Tasarımı Felsefesi) adlı kitabından alınmıştır. Bu kitap, yazılım geliştirmede karmaşıklığın özünü ele almakla kalmaz, aynı zamanda Parnas’ın yazılım dünyasına yön veren makalesi *[On the Criteria To Be Used in Decomposing Systems into Modules](https://www.win.tue.nl/~wstomv/edu/2ip30/references/criteria_for_modularization.pdf)* için yapılmış belki de en iyi yorumlardan birini içerir. Her iki kaynak da mutlaka okunması gerekenlerdendir.\n> \n> İlgili diğer yazılar:  \n> – [A Philosophy of Software Design vs Clean Code](https://github.com/johnousterhout/aposd-vs-clean-code)  \n> – [Clean Code önermeyi bırakmanın zamanı gelmiş olabilir](https://qntm.org/clean)  \n> – [Küçük Fonksiyonlar Zararlı Olabilir](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29)\n\nNot: Eğer burada “çok fazla sorumluluğu olan, şişirilmiş Tanrı (God) objelerini” savunduğumuzu düşünüyorsan, yanılıyorsun.\n\n## Sadece bir şeyden sorumlu olmak\n\nÇoğu zaman, belirsiz bir şekilde “bir modül sadece bir işi yapmalı, başka hiçbir şeyle uğraşmamalı” prensibini takip edip bir sürü yüzeysel modül yaratıyoruz. Peki, bu “bir iş” tam olarak ne? Mesela bir nesne yaratmak tek bir iş değil mi? O halde [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks) gibi sınıflar gayet yerinde gibi görünüyor, değil mi? Ama işin garibi, bu tür sınıfların isimleri ve arayüzleri, bizzat tüm implementasyonlarından çok daha zor kavranabiliyor. Böyle bir soyutlama ne demek oluyor? Burada bir şeyler yanlış gidiyor.\n\n> Böyle yüzeysel (shallow) bileşenler arasında sürekli gidip gelmek zihinsel olarak yorucu oluyor. Biz insanlar için **[doğrusal düşünme](https://blog.separateconcerns.com/2023-09-11-linear-code.html)** çok daha doğal ve kolaydır.\n\nSistemlerimizi, kullanıcılarımızı ve paydaşlarımızı memnun etmek için değiştiririz. Onlara karşı sorumluyuz.  \n\n> Bir modül, yalnızca ama yanlızca bir kullanıcıya veya paydaşa karşı sorumlu olmalıdır.  \n\nBu, Tek Sorumluluk İlkesi’nin (Single Responsibility Principle) tam da neyle ilgili olduğunu özetliyor. Basitçe söylemek gerekirse; eğer bir yerde bir hata yaparsak ve bunun sonucu olarak iki farklı iş biriminden şikayet geliyorsa, o zaman bu ilkeyi ihlal etmiş oluruz. Burada modülümüzde kaç farklı iş yaptığımızın bir önemi yoktur.\n\nAma günümüzde bile, bu kural bazen faydadan çok zarar verebilir. Çünkü bu ilke, tıpkı insanlar kadar farklı şekillerde yorumlanabiliyor. Daha iyi bir yaklaşım, bu kuralın ne kadar bilişsel yük oluşturduğuna bakmak olur. Bir değişikliğin bir yerde yapılmasının, farklı iş süreçlerinde zincirleme reaksiyonlar yaratabileceğini akılda tutmak zihinsel olarak oldukça yorucudur. İşin özü bu kadar; öğrenilmesi gereken karmaşık terimler yok.  \n\n## Çok sayıda shallow mikroservis\n\nBu shallow-deep modül prensibi, ölçekten bağımsızdır ve mikro servis mimarilerine de uygulanabilir. Çok sayıda shallow mikro servis hiçbir işe yaramaz — sektör artık “makro servis” denilen, yani o kadar da yüzeysel olmayan (=daha derin) servislere doğru yöneliyor. Bu aşırı ayrıştırılmış ve yüzeysel yapıların sonucunda ortaya çıkan en kötü ve düzeltilmesi en zor durumlardan biri de, sözde dağıtık ama gerçekte bağımlılıklarla örülmüş bir “dağıtık monolit”tir.\n\nBir keresinde beş kişilik bir yazılım ekibine sahip bir startupa danışmanlık verdim. Ekip, toplamda tam **17 (!) mikro servis** oluşturmuştu. Takvimden 10 ay gerideydiler ve projeyi canlıya almaya çok uzaktılar. Her yeni gereksinim, en az dört mikro serviste değişiklik yapmayı gerektiriyordu. Servisler arası entegrasyon alanında sorunları teşhis etmek neredeyse imkânsız hâle gelmişti. Hem pazara çıkış süresi (time to market) hem de geliştiricilerin üzerindeki bilişsel yük, kabul edilemeyecek kadar yüksekti. `🤯`  \n\nYeni bir sistemin belirsizliğiyle bu şekilde mi başa çıkmalıyız? İşin en başında doğru mantıksal sınırları belirlemek gerçekten çok zordur. Buradaki asıl mesele, **kararları mümkün olduğunca geç —ama hâlâ sorumluluk alabileceğin bir noktada— vermektir**, çünkü en fazla bilgiye ancak o zaman sahip olursun. Ama biz ne yapıyoruz? Daha en baştan ağ (network) katmanı ekleyerek, tasarıma dair kararlarımızı geri dönülmesi zor hâle getiriyoruz. Bu da sistemi şekillendirmeyi esnek olmaktan çıkarıp katı ve kırılgan bir yapıya sokuyor. Ekibin tek savunması şuydu: *“FAANG şirketleri mikro servis mimarisinin işe yaradığını kanıtladı.”*  \n**Kendinize gelin, hayal kurmayı bırakın.**\n\n[Tanenbaum-Torvalds tartışması](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate)'nda, Linux’un monolitik tasarımının hatalı ve demode olduğu, onun yerine mikro çekirdek (microkernel) mimarisinin kullanılmasının gerektiği savunuluyordu. Teorik ve estetik açıdan bakıldığında, mikro çekirdek tasarımı gerçekten de üstün görünüyordu. Ama işin pratik tarafına gelirsek — aradan otuz yıl geçti, **mikro çekirdek tabanlı GNU Hurd hâlâ geliştirme aşamasında**, buna karşılık **monolitik Linux her yerde**. Bu sayfa Linux ile çalışıyor, akıllı çaydanlığın bile Linux ile çalışıyor. **Monolitik** Linux ile.\n\nGerçekten iyi tasarlanmış, modülleri birbirinden net şekilde ayrılmış bir **monolit**, çoğu zaman bir yığın mikro servisten çok daha esnek olur. Ayrıca bakım sürecinde de çok daha az zihinsel çaba gerektirir. Ancak **farklı modülleri ayrı ayrı deploy edebilme ihtiyacı gerçekten kaçınılmaz hâle geldiğinde** — örneğin geliştirme ekibini büyütmek için— modüller arasına bir ağ katmanı (yani gelecekteki mikro servisler) eklemeyi düşünmelisin. Yoksa sırf “ileride lazım olur” diye mikro servis mimarisine geçmek, hem esnekliği azaltır hem de bilişsel yükü ciddi biçimde artırır.\n\n## Zengin özelliklere sahip diller\n\nSevdiğimiz programlama diline yeni özellikler eklendiğinde heyecanlanırız. Bu özellikleri öğrenmek için zaman harcar, sonrasında da onların üzerine kod yazmaya başlarız.\n\nEğer bir dilde çok fazla özellik varsa, sadece birkaç satır kod yazarken bile “şunu mu kullansam, bunu mu denesem” diye yarım saatimizi harcayabiliriz. Bu aslında zaman kaybıdır. Ama daha kötüsü şu: **Aylar sonra o koda geri döndüğünde, o düşünce sürecini baştan yaşamaya mecbur kalırsın!**\n\n**Sadece bu karmaşık programı anlamakla kalmazsın, aynı zamanda programcının —var olan tüm özellikler arasından— neden problemi bu şekilde çözmeyi tercih ettiğini de anlaman gerekir.** `🤯`\n\nBu sözler, başkası değil **Rob Pike** tarafından söylenmiştir.\n\n> Seçeneklerin sayısını sınırlayarak bilişsel yükü azalt.  \n\nDil özellikleri gayet güzeldir, **yeter ki birbirinden bağımsız (ortogonal) olsunlar.**\n\n<details>\n  <summary><b>20 yıllık C++ deneyimine sahip bir mühendisin düşünceleri ⭐️</b></summary>\n  <br>\n  Geçen gün RSS okuyucuma baktım, “C++” etiketi altında yaklaşık üç yüz okunmamış makalem olduğunu gördüm. Geçen yazdan beri C++ ile ilgili tek bir makale okumamışım ve buna rağmen harika hissediyorum!<br><br>\n  20 yıldır C++ kullanıyorum, hayatımın neredeyse üçte ikisi bu dilde geçti. Deneyimimin çoğu dilin en karmaşık ve karanlık köşeleriyle ilgili (mesela her türlü tanımsız davranışlar). Bu deneyim kolayca başkalarına aktarılamaz ve şimdi bunu bir kenara bırakmak biraz ürkütücü.<br><br>\n  Mesela, <code>||</code> operatörünün <code>requires ((!P<T> || !Q<T>))</code> ile <code>requires (!(P<T> || Q<T>))</code> arasında tamamen farklı anlamları var. İlki kısıtlar arası “veya” anlamındayken, ikincisi klasik mantıksal OR operatörü ve davranışları da farklı.<br><br>\n  Önceki C++ sürümlerinde trivial (basit) tipler için ayrılan belleğe sadece <code>memcpy</code> yaparak nesne yaşam döngüsünü başlatamazdınız. Bu, C++20 ile düzeltildi ama dilin bilişsel yükü yine de arttı.<br><br>\n  Bilişsel yük hep artıyor; düzeltmeler yapılsa da, neyin ne zaman düzeltildiğini, öncesini bilmek zorundayım. Sonuçta profesyonelim. C++’un güçlü olduğu miras desteği, demek ki bu eski karmaşıklıklarla karşılaşmaya devam edeceksiniz. Mesela geçen ay bir meslektaşım C++03’teki bir davranışı sordu. <code>🤯</code><br><br>\n  Initialization'ın 20 farklı yolu vardı, *Uniform initialization syntax* geldi ve 21 yol oldu. Ayrıca, initializer list’ten constructorların seçilmesi için kuralları hatırlayan var mı? En az bilgi kaybıyla implicit conversion... Ama eğer değer statik olarak biliniyorsa... <code>🤯</code><br><br> \n  <b>Bu artan bilişsel yük, yaptığımız işten kaynaklanmıyor. Alanın doğal bir karmaşıklığı da değil. Tarihten gelen, gereksiz bir yük (extraneous cognitive load).</b><br><br>\n  Kendime bazı kurallar koydum: Eğer bir kod satırı çok karmaşıksa ve standartları hatırlamam gerekiyorsa, o şekilde yazmamaya çalışıyorum. Standardın kendisi de yaklaşık 1500 sayfa.<br><br>\n  <b>C++’ı suçlamıyorum, dili çok seviyorum. Ama artık yoruldum.</b><br><br> \n  <p>Bu yazı için <a href=\"https://0xd34df00d.me\" target=\"_blank\">0xd34df00d</a>'a teşekkür ederim.</p>\n</details>\n\n## Business logic ve HTTP durum kodları\n\nBackend tarafında şu değerleri döndürüyoruz:  \n`401` JWT tokeninin süresi dolduğunda  \n`403` yeterli erişim izni olmadığında  \n`418` kullanıcı banlanmış olduğunda  \n\nFrontend mühendisleri, login işlevini gerçekleştirmek için backend API’sini kullanıyorlar. Bu sırada beyinlerinde geçici olarak şu bilişsel yükü oluşturmak zorunda kalıyorlar:  \n`401` JWT tokeninin süresi dolduğunda // `🧠+`, sadece geçici olarak bunu aklında tutmam gerek  \n`403` yeterli erişim izni olmadığında // `🧠++`  \n`418` kullanıcı banlanmış olduğunda // `🧠+++`  \n\nFrontend geliştiricileri, (umarız ki) kendi taraflarında bir `sayısal durum kodu → anlamı` **sözlüğü** oluştururlar. Böylece sonraki katkıda bulunanlar, bu eşlemeyi tekrar kendi kafalarında kurmak zorunda kalmazlar.\n\nSonra devreye QA (Kalite Güvence) mühendisleri girer:\n\"Hey, bana `403` durumu geldi, bu süresi dolmuş token mı yoksa yetersiz erişim mi?\"  \n**QA mühendisleri doğrudan teste geçemez, önce backend mühendislerinin oluşturduğu bilişsel yükü kendi kafalarında yeniden yaratmak zorundadırlar.**\"\n\nNeden bu özel eşlemeyi sürekli çalışma hafızamızda tutalım ki? İş detaylarını HTTP transfer protokolünden soyutlamak ve cevap gövdesinde kendini açıklayan kodlar döndürmek çok daha iyidir:\n\n```json\n{\n    \"code\": \"jwt_has_expired\"\n}\n```\n\nFrontend tarafındaki bilişsel yük: `🧠` (yeni, akılda tutulacak bilgi yok)  \nQA tarafındaki bilişsel yük: `🧠`\n\nAynı kural her türlü sayısal durum için geçerlidir (veritabanında ya da başka bir yerde) — **kendini açıklayan metinleri tercih edin**. Artık hafızayı optimize etmek için 640K bilgisayarların çağı değiliz.  \n\n> İnsanlar `401` ile `403` arasında tartışmakla zaman harcıyor, kendi zihinsel modellerine göre karar veriyorlar. Yeni geliştiriciler geliyor ve o düşünce sürecini tekrar yaratmak zorunda kalıyorlar. Kodunuz için “neden”leri (ADRs) belgelemiş olabilirsiniz; bu, yeni gelenlerin alınan kararları anlamasına yardımcı olur. Ama sonuçta bu çok da mantıklı değil. Hataları ya kullanıcı kaynaklı ya da sunucu kaynaklı olarak ayırabiliriz; onun dışında kalanlar ise biraz muğlak kalıyor. \n\nNot: “Kimlik doğrulama” (authentication) ile “yetkilendirme” (authorization) arasındaki farkı ayırt etmek genellikle zihinsel olarak yorucudur. Bilişsel yükü azaltmak için [\"giriş\" (login) ve \"izinler\" (permissions)](https://ntietz.com/blog/lets-say-instead-of-auth/) gibi daha basit terimler kullanabiliriz.\n\n## DRY prensibini yanlış kullanmak\n\n**Kendini Tekrar Etme (Don’t Repeat Yourself - DRY)** prensibi, yazılım mühendisi olarak öğretilen ilk ve temel prensiplerden biridir. Bu prensip o kadar içimize işlemiştir ki, fazladan birkaç satır kod görmek bile tahammül edilemez gelir. Genel olarak iyi ve temel bir kural olsa da, aşırı kullanıldığında başa çıkamayacağımız bilişsel yüke yol açabilir.\n\nGünümüzde herkes, mantıksal olarak ayrılmış bileşenler üzerine yazılım geliştiriyor. Bu bileşenler çoğunlukla, ayrı hizmetleri temsil eden birden fazla kod tabanına dağıtılmış durumda oluyor. Tekrarı tamamen ortadan kaldırmaya çalışırken, alakasız bileşenler arasında sıkı bir bağlılık (tight coupling) yaratma riski ortaya çıkar. Bunun sonucu olarak, bir bölümde yapılan değişiklikler, görünüşte alakasız diğer bölümlerde beklenmedik etkiler yaratabilir. Ayrıca, bu durum bireysel bileşenleri değiştirmeyi veya yenilemeyi zorlaştırır çünkü sistemin tamamını etkileyebilir. `🤯`  \n\nAslında aynı sorun tek bir modül içinde de ortaya çıkabilirdi. Ortak işlevselliği, uzun vadede gerçekten var olmayan benzerliklere dayanarak çok erken bir aşamada soyutlamaya çalışabilirsiniz. Bu da gereksiz soyutlamalar oluşturur ve bunlar üzerinde değişiklik yapmak ya da genişletmek zorlaşır.  \n\nRob Pike bir keresinde şöyle demiştir:\n\n> Biraz kopyalamak, biraz bağımlı olmaktan daha iyidir.  \n\nTekerleği yeniden icat etmemek adına öyle güçlü bir şekilde eğilimliyiz ki, kendi başımıza kolayca yazabileceğimiz küçük bir fonksiyonu kullanmak için büyük ve ağır kütüphaneleri içe aktarmaya hazırız.  \n\n**Ama aslında tüm bağımlılıklarınız sizin kodunuzdur.** Dışarıdan alınan bir kütüphanenin 10’dan fazla katmanlı stack trace’ini inceleyip neyin yanlış gittiğini anlamaya çalışmak (*çünkü hatalar olur*) gerçekten zor ve yorucu bir süreçtir.  \n\n## Bir framework ile sıkı bağlılık (tight coupling)\n\nFramework’lerde bolca “sihir” vardır. Bir framework’e çok fazla bağlı kaldığımızda, **gelecek tüm geliştiricilerin önce o “sihiri” öğrenmesini zorunlu kılmış oluruz.** Bu da aylar sürebilir. Framework’ler, MVP’leri birkaç gün içinde hayata geçirmemizi sağlasa da, uzun vadede gereksiz karmaşıklık ve bilişsel yük eklemeye meyillidirler.\n\nDahası, bir noktada framework’ler, mimariye uymayan yeni bir gereksinimle karşılaşıldığında ciddi bir kısıtlayıcı haline gelebilir. Bu noktadan sonra insanlar framework’ü çatallayıp (fork) kendi özel versiyonlarını yönetmek zorunda kalırlar. Yeni gelen birinin, herhangi bir değer katabilmek için bu özel framework’ü öğrenerek üstlenmesi gereken bilişsel yükü bir düşünün. `🤯`\n\n**Kesinlikle her şeyi baştan icat etmeyi savunmuyoruz!**\n\nKodu mümkün olduğunca framework'ten bağımsız yazabiliriz. İş mantığı framework’ün içinde yer almamalı, onun yerine framework bileşenlerini kullanmalı. Framework’ü çekirdek mantığınızın dışına koyun. Framework’ü bir kütüphane gibi kullanın. Böylece yeni katılımcılar, framework kaynaklı karmaşanın içinde kaybolmadan, ilk günden itibaren projeye değer katabilirler.  \n\n> [Frameworklerden neden nefret ediyorum](https://minds.md/benji/frameworks)\n\n## Katmanlı mimari\n\nBütün bunlarda belli bir mühendislik heyecanı var.\n\nBen de yıllarca Hexagonal/Onion Mimarisi’nin tutkulu bir savunucusu oldum. Bunu çeşitli projelerde kullandım ve diğer ekipleri de teşvik ettim. Ancak projelerimizin karmaşıklığı arttı, sadece dosya sayısı bile iki katına çıktı. Sanki çok fazla “glue code” yazıyormuşuz gibi hissettik. Sürekli değişen gereksinimler karşısında, birden fazla soyutlama katmanında değişiklik yapmak zorunda kalmak çok yorucu hale geldi. `🤯`\n\nSoyutlama (abstraction) aslında karmaşıklığı gizlemek için vardır; ancak burada sadece [dolaylılık (indirection)](https://fhur.me/posts/2024/thats-not-an-abstraction) ekliyor. Sorunu hızlıca çözmek için çağrılar arasında zıplayarak (jumping from call to call) neyin yanlış gittiğini ve neyin eksik olduğunu anlamak hayati bir gerekliliktir. Bu mimaride katmanlar arasındaki gevşek bağlantı (layer uncoupling) sebebiyle, hatanın meydana geldiği noktaya ulaşmak için çok daha fazla ve çoğu zaman birbirinden kopuk (disjointed) iz sürme işlemi (trace) gerekir. Her böyle iz sürme, sınırlı olan çalışma belleğimizde (working memory) yer kaplar ve bilişsel yükü (cognitive load) artırır. `🤯`  \n\nBu mimari başlangıçta sezgisel (intuitive) olarak mantıklı görünüyordu; ancak projelere uygulamaya çalıştıkça faydasından çok zarar verdiğini gördük. Sonunda tüm bu karmaşıklığı bırakıp klasik **dependency inversion principle** (bağımlılıkların tersine çevrilmesi ilkesi) lehine vazgeçtik. Artık **port/adapter gibi karmaşık terimler öğrenmeye gerek yok, gereksiz yatay soyutlama katmanları yok, ve fazladan bilişsel yük (cognitive load) yok**. Bu sayede kod daha sade, anlaşılır ve yönetilebilir hale geldi.\n\n<details>\n  <summary><b>Kodlama prensipleri ve deneyim eğrisi</b></summary>\n  <img src=\"img/complexity.png\"><br>\n  <a href=\"https://twitter.com/flaviocopes\">@flaviocopes</a>\n</details>\n\nEğer böyle bir katmanlandırmanın (layering) veritabanını veya diğer bağımlılıkları hızlıca değiştirmenize olanak sağlayacağını düşünüyorsanız, yanılıyorsunuz. Depolama katmanını değiştirmek birçok problem yaratır ve inanın, veri erişim katmanı için bazı soyutlamalara (abstraction) sahip olmak endişelerinizin en küçüğüdür. En iyi ihtimalle, soyutlamalar geçiş sürenizin yaklaşık %10’unu (eğer varsa) kurtarabilir; asıl zorluk veri modeli uyumsuzluklarında, iletişim protokollerinde, dağıtık sistemlerin getirdiği karmaşıklıklarda ve [örtük arayüzlerde (implicit interfaces)](https://www.hyrumslaw.com) yatar.  \n\n> Yeterli sayıda API kullanıcınız olduğunda,  \n> sözleşmede ne vaat ettiğiniz önemli değildir:  \n> sisteminizin tüm gözlemlenebilir davranışlarına  \n> birileri bağımlı olacaktır.\n\nBir depolama geçişi yaptık ve bu yaklaşık 10 ay sürdü. Eski sistem tek iş parçacıklıydı, dolayısıyla ortaya çıkan olaylar sıralıydı. Tüm sistemlerimiz bu gözlemlenen davranışa bağımlıydı. Bu davranış API sözleşmesinin bir parçası değildi, koda yansıtılmamıştı. Yeni dağıtık depolama bu garantiyi vermiyordu — olaylar sırasız (out-of-order) geliyordu. Yeni bir depolama adaptörü yazmak sadece birkaç saatimizi aldı, soyutlama sayesinde. **Ancak sonraki 10 ayı sırasız olaylar ve diğer zorluklarla uğraşarak geçirdik.** Artık soyutlamaların bileşenleri hızlıca değiştirmemize yardımcı olduğu söylemesi komik oluyor.  \n\n**Peki, böyle katmanlı bir mimarinin yüksek bilişsel yükünün bedelini neden ödeyelim ki, eğer bu ileride karşılığını vermiyorsa?** Üstelik çoğu durumda, o temel bileşeni değiştirme geleceği hiç gerçekleşmez.  \n\nBu mimariler temel değildir; onlar sadece daha temel prensiplerin öznel, yanlı yorumlarıdır. Neden bu öznel yorumlara dayanırsınız? Bunun yerine temel kuralları takip edin: Bağımlılıkları tersine çevirme prensibi (dependency inversion principle), tek gerçek kaynağı (single source of truth), bilişsel yük (cognitive load) ve bilgi gizleme (information hiding). İş mantığınız, veritabanı, kullanıcı arayüzü veya framework gibi düşük seviyeli modüllere bağlı olmamalıdır. Altyapıyı düşünmeden çekirdek mantığımız için testler yazabilmeliyiz, hepsi bu. [Tartışıma](https://github.com/zakirullin/cognitive-load/discussions/24).\"\n\nMimari adına katmanlar veya soyutlamalar eklemeyin. Sadece gerektiğinde pratik nedenlerle gerekçelendirilmiş bir genişletme noktası (extension point) ekleyin.\n\n[Soyutlama katmanları ücretsiz değildir](https://blog.jooq.org/why-you-should-not-implement-layered-architecture); bunlar sınırlı olan çalışma belleğimizde (working memory) tutulmak zorundadır.\n\n<div align=\"center\">\n  <img src=\"img/layers.png\" alt=\"Layers\" width=\"400\">\n</div>\n\n## Domain-driven design\n\nDomain-driven design’in (DDD) bazı harika noktaları var, ancak genellikle yanlış anlaşılıyor. İnsanlar 'DDD ile kod yazıyoruz' diyorlar, bu biraz garip çünkü DDD, çözüm alanı değil, problem alanı ile ilgilidir.\n\nUbiquitous language (her yerde kullanılan dil), domain (alan), bounded context (sınırlı bağlam), aggregate (küme), event storming (olay fırtınası) gibi kavramların hepsi problem alanıyla ilgilidir. Amaçları, alanla ilgili içgörüleri öğrenmemize ve sınırları belirlememize yardımcı olmaktır. DDD, geliştiricilerin, domain uzmanlarının ve iş insanlarının tek, birleşik bir dil kullanarak etkili bir şekilde iletişim kurmasını sağlar. Ancak, DDD’nin problem alanı ile ilgili bu yönlerine odaklanmak yerine, biz çoğu zaman belirli klasör yapıları, servisler, repository’ler ve diğer çözüm alanı tekniklerini vurgulamaya eğilimliyiz. \n\nDDD’yi yorumlama şeklimizin büyük olasılıkla benzersiz ve sübjektif (öznel) olduğunu söyleyebiliriz. Eğer bu anlayış üzerine kod yazarsak, yani gereksiz ve fazla bilişsel yük (cognitive load) yaratırsak — gelecekteki geliştiriciler zor durumda kalır. `🤯`  \n\nTakım Topolojileri, bilişsel yükü takımlar arasında daha iyi ve daha kolay anlaşılır şekilde bölmemize yardımcı olan bir çerçeve sunar. Mühendisler Takım topolojilerini öğrendikten sonra genellikle benzer zihinsel modeller geliştirirler. Öte yandan, DDD (Domain-Driven Design) farklı okuyucular için 10 farklı zihinsel model yaratıyor gibi görünüyor. Ortak bir zemin olmak yerine, gereksiz tartışmaların yaşandığı bir savaş alanına dönüşüyor.  \n\n## Bazı örnekler\n\n- Bizim mimarimiz standart bir CRUD uygulama mimarisi; [Postgres üzerine kurulmuş Python monolitik yapısı](https://danluu.com/simple-architectures/)\n- Instagram, sadece [3 mühendisle](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million) nasıl 14 milyon kullanıcıya ulaştı?\" \n- Bize ‘vay be, bu adamlar gerçekten [çok zeki](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)’ dedirten şirketlerin çoğu başarısız oldu.\n- Tüm sistemi birbirine bağlayan tek bir fonksiyon. Sistemin nasıl çalıştığını öğrenmek istiyorsan — [Bi' göz at bakalım](https://www.infoq.com/presentations/8-lines-code-refactoring).\n\nBu mimariler oldukça sıkıcı fakat anlaşılması kolaydır. Herkes fazla zihinsel çaba harcamadan kavrayabilir.\n\nMimari incelemelere junior geliştiricileri dahil edin. Onlar, zihinsel olarak zorlayıcı olan alanları tespit etmenize yardımcı olacaklardır.\n\n## Tanıdık projelerde bilişsel yük\n\n> **Sorun şu ki, tanıdıklık basitlikle aynı şey değil.** İkisi *aynı şekilde* hissedilir — yani az zihinsel çaba gerektiren bir ortamda rahatça hareket ediyormuşsunuz gibi — ama bunun sebepleri çok farklıdır. Kullandığınız her “zekice” (yani “kendi zevkinize göre yapılan”) ve standartlara uymayan hile, diğer herkes için öğrenme maliyeti getirir. Birileri o öğrenme sürecini tamamladıktan sonra kodla çalışmak onlar için daha az zor hale gelir. Bu yüzden, zaten aşina olduğunuz bir kodu nasıl basitleştireceğinizi fark etmek zordur. İşte bu yüzden “yeni gelen” kişinin, kurumsallaşmadan önce kodu eleştirmesini sağlamaya çalışırım!\n> \n> Muhtemelen önceki yazar(lar) bu devasa karmaşayı tek seferde değil, küçük küçük adımlarla yarattılar. Yani sen, şimdiye kadar bu karmaşayı bir arada anlamaya çalışan ilk kişisin.  \n> \n> Sınıfımda, bir gün baktığımız yüzlerce satırlık koşul içeren devasa bir SQL stored procedure’den bahsettim. Birisi, \"Bunun bu kadar kötü olmasına nasıl izin verilebilir?\" diye sordu. Ben de şunu söyledim: “Koşul sayısı 2 veya 3 iken, bir tane daha eklemenin pek bir farkı olmaz. Koşul sayısı 20 veya 30’a geldiğinde ise, bir tane daha eklemek artık hiç fark etmez!”  \n> \n> Kod tabanı üzerinde “basitleştiren bir güç” yoktur, sadece sizin kasıtlı olarak yaptığınız tercihler vardır. Basitleştirmek çaba gerektirir... insanlarsa çoğu zaman aceleyle hareket ederler. \n> \n> *Yorumları için [Dan North](https://dannorth.net)’a teşekkürler.*  \n\nEğer projenin zihinsel modellerini uzun süreli belleğinize yerleştirmişseniz, yüksek bilişsel yük hissetmezsiniz.  \n\n<div align=\"center\">\n  <img src=\"img/mentalmodelsv15.png\" alt=\"Mental models\" width=\"700\">\n</div>\n\nÖğrenilecek zihinsel model (mental model) ne kadar çoksa, yeni bir geliştiricinin değer yaratması o kadar uzun sürer.  \n\nProjene yeni kişiler katıldığında, kafalarının ne kadar karıştığını ölçmeye çalış. (Eşli programlama — pair programming — bu konuda yardımcı olabilir.) Eğer üst üste yaklaşık 40 dakikadan fazla kafa karışıklığı yaşıyorlarsa, kodunda geliştirilmesi gereken şeyler var demektir.  \n\nEğer bilişsel yükü (cognitive load) düşük tutarsan, insanlar şirkete katıldıktan sonraki ilk birkaç saat içinde kod tabanına katkıda bulunabilirler.\n\n## Sonuç\n\nBir an için ikinci bölümde çıkarım yaptığımız şeyin aslında doğru olmadığını hayal et. Eğer durum buysa, az önce reddettiğimiz sonuçla birlikte, önceki bölümde doğru kabul ettiğimiz çıkarımların da doğru olmayabileceği ortaya çıkar. `🤯`\n\nBunu hissediyor musun? Sadece anlamı yakalamak için makalenin her yerine zıplamak zorunda kalmıyorsun (yüzeysel modüller!), aynı zamanda bu paragrafın kendisi de anlaması zor. Kafanda gereksiz bir bilişsel yük yarattık. **Bunu meslektaşlarına yapma.**\n\n<div align=\"center\">\n  <img src=\"img/smartauthorv14thanksmari.png\" alt=\"Smart author\" width=\"600\">\n</div>\n\nYaptığımız işin doğasında olan zorlayıcı kısımlar (intrinsic cognitive load) dışında kalan tüm gereksiz zihinsel yükü azaltmalıyız.\n\n---\n\n[LinkedIn](https://www.linkedin.com/in/zakirullin/), [X](https://twitter.com/zakirullin), [GitHub](https://github.com/zakirullin)\n\n[Daha sade ve okunabilir versiyon (İngilizce)](https://minds.md/zakirullin/cognitive)\n\n<details>\n    <summary><b>Yorumlar</b></summary>\n    <br>\n    <p><strong>Rob Pike</strong><br>Nice article.</p>\n    <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>\n    <p><strong><a href=\"https://x.com/elonmusk/status/1872346903792566655\" target=\"_blank\">Elon Musk</a></strong><br>True.</p>\n    <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>\n    <p>The irony? Many of these complexity-inducing patterns were implemented in the name of \"clean code.\"</p>\n    <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>\n    <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>\n    <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>\n    <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>\n    <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>\n    <p>Sometimes the simplest solution is the best one, even in a complex system.</p>\n    <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>\n    <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>\n    <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>\n    <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>\n</details>\n"
  },
  {
    "path": "README.vi.md",
    "content": "# Cognitive load mới là thứ quan trọng\n\n[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) | [Vietnamese](README.vi.md)\n\n*Đây là tài liệu sống, cập nhật lần cuối: **Tháng 10/2025.** Mọi đóng góp đều được chào đón!*\n\n## Giới thiệu\nCó quá nhiều buzzword và best practice ngoài kia, nhưng phần lớn đều thất bại. Chúng thất bại vì chúng được tưởng tượng ra, không phải thực tế. Những ý tưởng này dựa trên thẩm mỹ và đánh giá chủ quan. Chúng ta cần thứ gì đó fundamental hơn, thứ không thể sai được.\n\nĐôi khi chúng ta cảm thấy bối rối khi đọc code. Sự bối rối tốn thời gian và tiền bạc. Sự bối rối được gây ra bởi *cognitive load* cao. Đây không phải một khái niệm trừu tượng fancy, mà là **một giới hạn cơ bản của con người.** Nó không phải tưởng tượng, nó ở đó và chúng ta cảm nhận được.\n\nVì chúng ta dành nhiều thời gian đọc và hiểu code hơn là viết nó, chúng ta nên liên tục tự hỏi liệu mình có đang nhét quá nhiều cognitive load vào code hay không.\n\n## Cognitive load\n> Cognitive load là lượng suy nghĩ mà một developer cần để hoàn thành một task.\n\nKhi đọc code, bạn đưa các thứ như giá trị biến, control flow logic và call sequence vào đầu. Một người bình thường có thể giữ khoảng [bốn chunk như vậy](https://github.com/zakirullin/cognitive-load/issues/16) trong working memory. Khi cognitive load đạt ngưỡng này, việc hiểu trở nên khó khăn hơn nhiều.\n\n*Giả sử chúng ta được yêu cầu fix bug cho một project hoàn toàn xa lạ. Người ta nói có một developer thông minh đã contribute vào đó. Nhiều kiến trúc xịn, library fancy và công nghệ trendy được sử dụng. Nói cách khác, **tác giả đã tạo ra cognitive load cao cho chúng ta.***\n\n<div align=\"center\">\n  <img src=\"/img/cognitiveloadv6.png\" alt=\"Cognitive load\" width=\"750\">\n</div>\n\nChúng ta nên giảm cognitive load trong project càng nhiều càng tốt.\n\n<details>\n  <summary><b>Cognitive load và interruption</b></summary>\n  <div align=\"center\">\n    <img src=\"img/interruption.jpeg\" width=\"480\">\n  </div>\n</details>\n\n> Chúng ta sẽ dùng \"cognitive load\" theo nghĩa informal; đôi khi nó khớp với khái niệm khoa học về Cognitive Load, nhưng chúng ta không biết chính xác chỗ nào khớp và chỗ nào không.\n\n## Các loại cognitive load\n**Intrinsic** - do độ khó vốn có của task gây ra. Không thể giảm được, nó nằm trong bản chất của software development.\n\n**Extraneous** - do cách trình bày thông tin tạo ra. Do các yếu tố không liên quan trực tiếp đến task, như thói quen của tác giả thông minh. Có thể giảm đáng kể. Chúng ta sẽ tập trung vào loại cognitive load này.\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Intrinsic vs Extraneous\" width=\"600\">\n</div>\n\nCùng nhảy thẳng vào các ví dụ thực tế cụ thể về extraneous cognitive load.\n\n---\n\nChúng ta sẽ gọi level của cognitive load như sau:\n`🧠`: working memory trong lành, zero cognitive load\n`🧠++`: hai fact trong working memory, cognitive load tăng\n`🤯`: cognitive overload, hơn 4 fact\n\n> Não bộ chúng ta phức tạp và chưa được khám phá nhiều hơn thế, nhưng ta có thể dùng model đơn giản này.\n\n## Conditional phức tạp\n```go\nif val > someConstant // 🧠+\n    && (condition2 || condition3) // 🧠+++, prev cond phải true, một trong c2 hoặc c3 phải true\n    && (condition4 && !condition5) { // 🤯, tới đây ta đã rối\n    ...\n}\n```\n\nĐưa vào các biến trung gian với tên có nghĩa:\n```go\nisValid = val > someConstant\nisAllowed = condition2 || condition3\nisSecure = condition4 && !condition5\n// 🧠, không cần nhớ các condition, có biến mô tả rồi\nif isValid && isAllowed && isSecure {\n    ...\n}\n```\n\n## Nested if\n```go\nif isValid { // 🧠+, okay code nested chỉ apply cho valid input\n    if isSecure { // 🧠++, ta chỉ làm stuff cho valid và secure input\n        stuff // 🧠+++\n    }\n}\n```\n\nSo sánh với early return:\n```go\nif !isValid\n    return\n\nif !isSecure\n    return\n\n// 🧠, ta không quan tâm early return ở trên, nếu tới đây thì mọi thứ OK\n\nstuff // 🧠+\n```\n\nTa có thể focus vào happy path thôi, giải phóng working memory khỏi các precondition.\n\n## Ác mộng kế thừa\nTa được yêu cầu thay đổi vài thứ cho admin user: `🧠`\n\n`AdminController extends UserController extends GuestController extends BaseController`\n\nỒ, một phần functionality nằm trong `BaseController`, coi thử: `🧠+`\nRole mechanic cơ bản được giới thiệu trong `GuestController`: `🧠++`\nMọi thứ được thay đổi một phần trong `UserController`: `🧠+++`\nCuối cùng tới đây, `AdminController`, code thôi! `🧠++++`\n\nÀ đợi, còn có `SuperuserController` extend `AdminController`. Nếu sửa `AdminController` ta có thể làm hỏng class kế thừa, nên xem `SuperuserController` trước: `🤯`\n\nƯu tiên composition hơn inheritance. Ta sẽ không đi sâu - có [rất nhiều tài liệu](https://www.youtube.com/watch?v=hxGOiiR9ZKg) về việc này.\n\n## Quá nhiều method, class hoặc module nhỏ\n> Method, class và module có thể thay thế cho nhau trong context này.\n\nCác mantra kiểu \"method nên ngắn hơn 15 dòng code\" hay \"class nên nhỏ\" hóa ra hơi sai.\n\n**Deep module** - interface đơn giản, functionality phức tạp\n**Shallow module** - interface tương đối phức tạp so với functionality nhỏ mà nó cung cấp\n\n<div align=\"center\">\n  <img src=\"/img/deepmodulev8.png\" alt=\"Deep module\" width=\"700\">\n</div>\n\nCó quá nhiều shallow module có thể gây khó khăn cho việc hiểu project. **Không chỉ phải nhớ responsibility của từng module, mà còn phải nhớ tất cả interaction giữa chúng.** Để hiểu mục đích của shallow module, trước tiên ta phải xem functionality của các module liên quan. Nhảy giữa các component shallow như vậy rất mệt mỏi về mặt tinh thần, <a target=\"_blank\" href=\"https://blog.separateconcerns.com/2023-09-11-linear-code.html\">linear thinking</a> tự nhiên hơn với con người.\n\n> Information hiding là tối quan trọng, và ta không ẩn được nhiều complexity trong shallow module.\n\nTôi có hai pet project, cả hai đều khoảng 5K dòng code. Project đầu có 80 shallow class, trong khi project thứ hai chỉ có 7 deep class. Tôi không maintain hai project này trong một năm rưỡi.\n\nKhi quay lại, tôi nhận ra rằng cực kỳ khó để tách rõ tất cả interaction giữa 80 class trong project đầu. Tôi phải rebuild một lượng cognitive load khổng lồ trước khi có thể bắt đầu code. Mặt khác, tôi có thể nắm bắt project thứ hai nhanh chóng vì nó chỉ có vài deep class với interface đơn giản.\n\n> Component tốt nhất là những component cung cấp functionality mạnh mẽ nhưng có interface đơn giản.\n>\n> *John Ousterhout, A Philosophy of Software Design*\n\nInterface của Unix I/O rất đơn giản. Nó chỉ có năm basic call:\n```python\nopen(path, flags, permissions)\nread(fd, buffer, count)\nwrite(fd, buffer, count)\nlseek(fd, offset, referencePosition)\nclose(fd)\n```\n\nImplementation hiện đại của interface này có **hàng trăm nghìn dòng code.** Rất nhiều complexity được ẩn bên dưới. Nhưng nó dễ dùng nhờ interface đơn giản.\n\n> Ví dụ deep module này được lấy từ sách [A Philosophy of Software Design](https://web.stanford.edu/~ouster/cgi-bin/book.php) của John Ousterhout. Không chỉ cuốn sách này bàn về bản chất của complexity trong software development, mà còn có cách giải thích hay nhất về paper có ảnh hưởng của 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). Cả hai đều là tài liệu bắt buộc phải đọc. Các tài liệu liên quan khác: [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).\n\n<details>\n    <summary><b>Những thứ quan trọng nên to, ví dụ</b></summary>\n    <br>\n    <div align=\"center\">\n        <img src=\"/img/dirty.png\" alt=\"Clean vs Dirty\" width=\"600\">\n    </div>\n    <blockquote>Nếu bạn cho phép các function \"crux\" quan trọng to hơn (\"dirty\"), sẽ dễ dàng hơn để pick chúng ra khỏi biển function, chúng rõ ràng là quan trọng: nhìn vào chúng, chúng to!</blockquote>\n    Hình này được lấy từ bài <a href=\"https://htmx.org/essays/codin-dirty/\" target=\"_blank\">Codin' Dirty</a> của Carson Gross. Bạn sẽ tìm thấy <a href=\"https://htmx.org/essays/codin-dirty/#real-world-examples\" target=\"_blank\">ví dụ thực tế</a> về deep function ở đó.\n</details>\n\nP.S. Nếu bạn nghĩ chúng ta đang ủng hộ God object phình to với quá nhiều responsibility, bạn hiểu sai rồi.\n\n## Responsible cho một thing\nQuá thường xuyên, chúng ta tạo ra nhiều shallow module, theo một nguyên tắc mơ hồ \"một module nên responsible cho một, và chỉ một, thing\". Cái \"một thing\" mơ hồ này là gì? Khởi tạo một object là một thing, đúng không? Vậy [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks) có vẻ ổn. **Tên và interface của các class như vậy có xu hướng tốn não hơn toàn bộ implementation của chúng, đó là abstraction kiểu gì?** Có gì đó sai sai.\n\nChúng ta thay đổi hệ thống để thỏa mãn user và stakeholder. Chúng ta responsible với họ.\n\n> Một module nên responsible với một, và chỉ một, user hoặc stakeholder.\n\nĐây là bản chất của Single Responsibility Principle. Nói đơn giản, nếu ta gây ra bug ở một chỗ, rồi hai business people khác nhau đến phàn nàn, ta đã vi phạm nguyên tắc. Nó không liên quan gì đến số lượng thing ta làm trong module.\n\nNhưng ngay cả bây giờ, rule này có thể gây hại nhiều hơn lợi. Nguyên tắc này có thể được hiểu theo nhiều cách khác nhau tùy mỗi người. Approach tốt hơn là nhìn vào lượng cognitive load mà nó tạo ra. Rất tốn não để nhớ rằng thay đổi ở một chỗ có thể trigger chuỗi phản ứng qua các business stream khác nhau. Và chỉ vậy thôi, không cần học fancy term gì cả.\n\n## Quá nhiều shallow microservice\nNguyên tắc shallow-deep module này scale-agnostic, và ta có thể áp dụng nó cho microservices architecture. Quá nhiều shallow microservice không tốt - ngành đang hướng tới \"macroservice\", tức là service không quá shallow (=deep). Một trong những hiện tượng tệ nhất và khó fix nhất là distributed monolith, thường là kết quả của việc phân tách shallow quá mức này.\n\nTôi từng tư vấn cho một startup nơi team năm developer tạo ra 17(!) microservice. Họ chậm 10 tháng so với lịch trình và không gần tới public release. Mỗi requirement mới dẫn đến thay đổi trong 4+ microservice. Mất vô cùng nhiều thời gian để reproduce và debug issue trong distributed system như vậy. Cả time to market và cognitive load đều cao không thể chấp nhận. `🤯`\n\nĐây có phải cách đúng để tiếp cận uncertainty của hệ thống mới? Cực kỳ khó để xác định đúng logical boundary ngay từ đầu. Điều quan trọng là đưa ra quyết định càng muộn càng tốt, vì lúc đó bạn có nhiều thông tin nhất. Bằng cách đưa network layer vào ngay từ đầu, ta làm cho design decision khó revert ngay từ lúc bắt đầu. Lý do duy nhất của team là: \"Các công ty FAANG đã chứng minh microservices architecture hiệu quả\". *Hello, bạn phải thôi mơ lớn đi.*\n\n[Tranh luận Tanenbaum-Torvalds](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate) cho rằng thiết kế monolithic của Linux là lỗi thời và nên dùng microkernel architecture thay thế. Thật vậy, thiết kế microkernel có vẻ vượt trội \"từ góc độ lý thuyết và thẩm mỹ\". Về mặt thực tế - ba thập kỷ sau, GNU Hurd dựa trên microkernel vẫn đang phát triển, còn monolithic Linux thì ở khắp nơi. Trang này chạy bằng Linux, ấm đun nước thông minh của bạn cũng chạy Linux. Bằng monolithic Linux.\n\nMột monolith được craft tốt với các module thật sự isolated thường linh hoạt hơn nhiều so với một đống microservice. Nó cũng cần ít cognitive effort hơn nhiều để maintain. Chỉ khi nhu cầu deploy riêng biệt trở nên quan trọng, như scale development team, bạn mới nên xem xét thêm network layer giữa các module, các microservice tương lai.\n\n## Ngôn ngữ feature-rich\nChúng ta hào hứng khi các feature mới được release trong ngôn ngữ yêu thích. Chúng ta dành thời gian học các feature này, build code dựa trên chúng.\n\nNếu có nhiều feature, ta có thể tốn nửa giờ chơi với vài dòng code, để dùng feature này hay feature kia. Và đó hơi lãng phí thời gian. Nhưng tệ hơn, **khi bạn quay lại sau, bạn phải recreate lại thought process đó!**\n\n**Bạn không chỉ phải hiểu chương trình phức tạp này, bạn còn phải hiểu tại sao một programmer quyết định đây là cách tiếp cận vấn đề từ các feature có sẵn.** `🤯`\n\nNhững phát biểu này được đưa ra bởi không ai khác ngoài Rob Pike.\n\n> Giảm cognitive load bằng cách giới hạn số lượng choice.\n\nLanguage feature là OK, miễn là chúng orthogonal với nhau.\n\n<details>\n  <summary><b>Suy nghĩ từ một engineer với 20 năm kinh nghiệm C++ ⭐️</b></summary>\n  <br>\n  Hôm trước tôi nhìn RSS reader và nhận ra có khoảng ba trăm bài chưa đọc dưới tag \"C++\". Tôi chưa đọc một bài nào về ngôn ngữ này từ mùa hè năm ngoái, và tôi cảm thấy tuyệt!<br><br>\n  Tôi đã dùng C++ được 20 năm rồi, gần hai phần ba cuộc đời tôi. Phần lớn kinh nghiệm của tôi là deal với những góc tối nhất của ngôn ngữ (như undefined behaviour đủ loại). Đây không phải kinh nghiệm tái sử dụng được, và hơi đáng sợ khi phải bỏ hết bây giờ.<br><br>\n  Kiểu như, bạn có thể tưởng tượng được không, token <code>||</code> có nghĩa khác nhau trong <code>requires ((!P&lt;T&gt; || !Q&lt;T&gt;))</code> và trong <code>requires (!(P&lt;T&gt; || Q&lt;T&gt;))</code>. Cái đầu là constraint disjunction, cái thứ hai là operator logical OR quen thuộc, và chúng hoạt động khác nhau.<br><br>\n  Bạn không thể allocate space cho trivial type và chỉ <code>memcpy</code> một set byte vào đó mà không cần thêm effort - điều đó sẽ không bắt đầu lifetime của một object. Đây là tình huống trước C++20. Nó đã được fix trong C++20, nhưng cognitive load của ngôn ngữ chỉ tăng lên.<br><br>\n  Cognitive load liên tục tăng, dù mọi thứ được fix. Tôi phải biết cái gì được fix, khi nào nó được fix, và trước đó nó như thế nào. Tôi là một professional mà. Chắc chắn, C++ giỏi về legacy support, điều đó cũng có nghĩa là bạn <b>sẽ gặp</b> legacy đó. Ví dụ, tháng trước một đồng nghiệp hỏi tôi về một behaviour trong C++03. <code>🤯</code><br><br>\n  Có 20 cách initialization. Uniform initialization syntax được thêm vào. Bây giờ có 21 cách initialization. Nhân tiện, có ai nhớ rule để chọn constructor từ initializer list không? Một cái gì đó về implicit conversion với ít loss thông tin nhất, <i>nhưng nếu</i> giá trị được biết statically thì... <code>🤯</code><br><br>\n  <b>Cognitive load tăng lên này không phải do business task đang làm. Không phải intrinsic complexity của domain. Nó chỉ ở đó do lý do lịch sử</b> (<i>extraneous cognitive load</i>).<br><br>\n  Tôi phải nghĩ ra vài rule. Kiểu như, nếu dòng code đó không rõ ràng và tôi phải nhớ standard, tốt hơn là không viết kiểu đó. Standard dài khoảng 1500 trang, nhân tiện.<br><br>\n  <b>Tôi hoàn toàn không blame C++.</b> Tôi yêu ngôn ngữ này. Chỉ là tôi mệt rồi.<br><br>\n  <p>Cảm ơn <a href=\"https://0xd34df00d.me\" target=\"_blank\">0xd34df00d</a> đã viết.</p>\n</details>\n\n## Business logic và HTTP status code\nỞ backend ta return:\n`401` cho expired JWT token\n`403` cho không đủ access\n`418` cho banned user\n\nCác engineer ở frontend dùng backend API để implement login functionality. Họ sẽ phải tạm thời tạo cognitive load sau trong đầu:\n`401` là cho expired JWT token // `🧠+`, ok chỉ nhớ tạm thời\n`403` là cho không đủ access // `🧠++`\n`418` là cho banned user // `🧠+++`\n\nFrontend developer sẽ (hy vọng) tạo một loại dictionary `numeric status -> meaning` ở phía họ, để các contributor thế hệ sau không phải recreate mapping này trong đầu.\n\nRồi QA engineer vào cuộc:\n\"Hey, tôi nhận được status `403`, đó là expired token hay không đủ access?\"\n**QA engineer không thể nhảy thẳng vào testing, vì trước tiên họ phải recreate cognitive load mà engineer backend đã tạo.**\n\nTại sao phải giữ custom mapping này trong working memory? Tốt hơn là abstract business detail ra khỏi HTTP transfer protocol, và return code tự mô tả trực tiếp trong response body:\n```json\n{\n    \"code\": \"jwt_has_expired\"\n}\n```\n\nCognitive load ở phía frontend: `🧠` (fresh, không có fact nào được giữ trong đầu)\nCognitive load ở phía QA: `🧠`\n\nRule tương tự áp dụng cho tất cả loại numeric status (trong database hay bất kỳ đâu) - **ưu tiên string tự mô tả.** Chúng ta không còn ở thời đại máy 640K để optimize memory nữa.\n\n> Mọi người dành thời gian tranh luận giữa `401` và `403`, đưa ra quyết định dựa trên mental model riêng của họ. Developer mới vào phải recreate thought process đó. Bạn có thể đã document \"why\" (ADR) cho code, giúp newcomer hiểu quyết định đã đưa ra. Nhưng cuối cùng nó chẳng có ý nghĩa gì. Ta có thể tách error thành user-related hoặc server-related, nhưng ngoài ra thì mọi thứ hơi mơ hồ.\n\nP.S. Thường rất tốn não để phân biệt giữa \"authentication\" và \"authorization\". Ta có thể dùng term đơn giản hơn như [\"login\" và \"permission\"](https://ntietz.com/blog/lets-say-instead-of-auth/) để giảm cognitive load.\n\n## Lạm dụng nguyên tắc DRY\n\nDo not repeat yourself - đó là một trong những nguyên tắc đầu tiên bạn được dạy khi là software engineer. Nó được nhúng sâu vào bản thân chúng ta đến nỗi ta không thể chịu được việc có thêm vài dòng code. Mặc dù nhìn chung là một rule tốt và fundamental, khi overuse nó dẫn đến cognitive load mà ta không thể handle.\n\nNgày nay, mọi người build software dựa trên các component tách biệt về mặt logic. Thường chúng được phân phối giữa nhiều codebase đại diện cho các service riêng. Khi bạn cố gắng loại bỏ mọi repetition, bạn có thể tạo ra tight coupling giữa các component không liên quan. Kết quả là, thay đổi ở một phần có thể có hậu quả ngoài ý muốn ở các khu vực khác có vẻ không liên quan. Nó cũng có thể cản trở khả năng replace hoặc modify từng component mà không ảnh hưởng toàn bộ hệ thống. `🤯`\n\nThực tế, vấn đề tương tự nảy sinh ngay cả trong một module đơn lẻ. Bạn có thể extract common functionality quá sớm, dựa trên sự giống nhau có thể không thực sự tồn tại về lâu dài. Điều này có thể dẫn đến các abstraction không cần thiết khó modify hoặc extend.\n\nRob Pike từng nói:\n\n> A little copying is better than a little dependency.\n\nChúng ta bị cám dỗ để không reinvent bánh xe mạnh đến nỗi sẵn sàng import các library to nặng để dùng một function nhỏ mà ta dễ dàng viết được.\n\n**Tất cả dependency của bạn là code của bạn.** Đi qua 10+ level của stack trace của library đã import và tìm ra chỗ sai (*vì mọi thứ đều sai*) rất đau đớn.\n\n## Tight coupling với framework\nCó rất nhiều \"magic\" trong framework. Bằng cách dựa quá nhiều vào framework, **ta buộc tất cả developer sau này phải học \"magic\" đó trước.** Có thể mất hàng tháng. Mặc dù framework cho phép ta launch MVP trong vài ngày, về lâu dài chúng có xu hướng thêm complexity và cognitive load không cần thiết.\n\nTệ hơn, tại một thời điểm nào đó framework có thể trở thành constraint đáng kể khi đối mặt với requirement mới không fit architecture. Từ đây mọi người fork framework và maintain custom version của riêng họ. Tưởng tượng lượng cognitive load mà một newcomer phải build (tức là học custom framework này) để deliver bất kỳ value nào. `🤯`\n\n**Chúng ta hoàn toàn không advocate việc invent mọi thứ from scratch!**\n\nTa có thể viết code theo cách hơi framework-agnostic. Business logic không nên nằm trong framework; thay vào đó, nó nên dùng các component của framework. Đặt framework ra ngoài core logic của bạn. Dùng framework theo kiểu library. Điều này cho phép contributor mới add value từ ngày đầu tiên, không cần phải đi qua debris của framework-related complexity trước.\n\n> [Why I Hate Frameworks](https://minds.md/benji/frameworks)\n\n## Layered architecture\nCó một sự hào hứng về engineering đối với tất cả thứ này.\n\nBản thân tôi từng là người ủng hộ nhiệt thành Hexagonal/Onion Architecture trong nhiều năm. Tôi dùng nó ở đây và kia và khuyến khích các team khác làm như vậy. Độ phức tạp của project tăng lên, số lượng file tăng gấp đôi. Cảm giác như ta đang viết rất nhiều glue code. Với requirement thay đổi liên tục, ta phải thay đổi qua nhiều layer của abstraction, tất cả trở nên tẻ nhạt. `🤯`\n\n**Abstraction được cho là ẩn complexity, ở đây nó chỉ thêm [indirection](https://fhur.me/posts/2024/thats-not-an-abstraction).** Nhảy từ call này sang call khác để đọc theo và tìm ra chỗ sai và thiếu cái gì là requirement quan trọng để nhanh chóng giải quyết vấn đề. Với việc layer uncoupling của architecture này, nó cần một factor exponential của các trace thêm, thường không liên tục, để đến điểm xảy ra lỗi. Mỗi trace như vậy chiếm chỗ trong limited working memory của ta. `🤯`\n\n<div align=\"center\">\n  <img src=\"/img/layers.png\" alt=\"Layers\" width=\"400\">\n</div>\n\nArchitecture này là thứ có vẻ hợp lý ban đầu, nhưng mỗi lần ta thử áp dụng vào project thì nó gây hại nhiều hơn lợi. Chúng ta dành nhiều năm cho hoạt động tinh thần không cần thiết và viết glue code vô dụng không có business value rõ ràng. Ngược lại, ta làm mọi thứ tệ hơn cho business bằng cách buộc newcomer phải học approach (mental model) của ta trước. Time to market xấu đi. Cuối cùng, ta từ bỏ tất cả để ủng hộ dependency inversion principle cũ kỹ tốt đẹp. **Không có port/adapter term để học, không có layer horizontal abstraction không cần thiết, không có extraneous cognitive load.**\n\nNếu bạn nghĩ rằng layering như vậy sẽ cho phép bạn nhanh chóng replace database hoặc dependency khác, bạn nhầm rồi. Thay đổi storage gây ra nhiều vấn đề, và tin tôi đi, có một vài abstraction cho data access layer là ít lo lắng nhất của bạn. Tốt nhất, abstraction có thể tiết kiệm khoảng 10% thời gian migration (nếu có), pain thực sự nằm ở data model incompatibility, communication protocol, distributed system challenge, và [implicit interface](https://www.hyrumslaw.com).\n\n> Với đủ số lượng user của một API,\n> không quan trọng bạn promise gì trong contract:\n> tất cả observable behaviour của hệ thống\n> sẽ được ai đó depend vào.\n\nChúng tôi đã làm storage migration, và mất khoảng 10 tháng. Hệ thống cũ là single-threaded, nên các event exposed là sequential. Tất cả hệ thống của chúng tôi depend vào observed behaviour đó. Behaviour này không phải part của API contract, không được phản ánh trong code. Storage mới distributed không có guarantee đó - các event ra ngoài out-of-order. Chúng tôi chỉ mất vài giờ code storage adapter mới, nhờ abstraction. **Chúng tôi dành 10 tháng tiếp theo để deal với out-of-order event và các challenge khác.** Giờ thật buồn cười khi nói abstraction giúp ta replace component nhanh chóng.\n\n**Vậy, tại sao phải trả giá của cognitive load cao cho layered architecture như vậy, nếu nó không pay off trong tương lai?** Hơn nữa, trong hầu hết trường hợp, tương lai thay thế core component đó không bao giờ xảy ra.\n\nCác architecture này không fundamental, chúng chỉ là hậu quả chủ quan, biased của các nguyên tắc fundamental hơn. Tại sao rely vào những interpretation chủ quan đó? Follow các rule fundamental thay thế: dependency inversion principle, single source of truth, cognitive load và information hiding. Business logic không nên depend vào low-level module như database, UI hay framework. Ta nên có thể viết test cho core logic mà không lo về infrastructure, và chỉ vậy thôi. [Thảo luận](https://github.com/zakirullin/cognitive-load/discussions/24).\n\nĐừng thêm layer abstraction vì architecture. Thêm chúng khi bạn cần extension point được justify bởi lý do thực tế.\n\n**[Layer abstraction không free](https://blog.jooq.org/why-you-should-not-implement-layered-architecture), chúng phải được giữ trong limited working memory của ta.**\n\n## Domain-driven design\nDomain-driven design có một vài điểm tốt, mặc dù nó thường bị hiểu sai. Mọi người nói, \"Chúng ta viết code trong DDD\", hơi lạ, vì DDD nhiều về problem space hơn là solution space.\n\nUbiquitous language, domain, bounded context, aggregate, event storming đều về problem space. Chúng được dùng để giúp ta học insight về domain và extract boundary. DDD cho phép developer, domain expert và business people giao tiếp hiệu quả bằng một ngôn ngữ đơn nhất, thống nhất. Thay vì focus vào các problem space aspect của DDD này, ta có xu hướng nhấn mạnh folder structure cụ thể, service, repository, và các solution space technique khác.\n\nRất có thể cách ta interpret DDD là duy nhất và chủ quan. Và nếu ta build code dựa trên sự hiểu biết này, tức là nếu ta tạo ra nhiều extraneous cognitive load - các developer tương lai sẽ khốn khổ. `🤯`\n\nTeam Topologies cung cấp framework tốt hơn nhiều, dễ hiểu hơn giúp ta split cognitive load giữa các team. Engineer có xu hướng phát triển mental model hơi giống nhau sau khi học về Team Topologies. DDD, mặt khác, dường như tạo ra 10 mental model khác nhau cho 10 người đọc khác nhau. Thay vì là common ground, nó trở thành battleground cho các cuộc tranh luận không cần thiết.\n\n## Cognitive load trong familiar project\n\n> Vấn đề là **familiarity không giống simplicity.** Chúng *cảm thấy* giống nhau — cùng sự dễ dàng di chuyển qua một không gian mà không cần nhiều mental effort — nhưng vì những lý do rất khác nhau. Mỗi trick \"clever\" (đọc: \"tự chiều chuộng\") và non-idiomatic mà bạn dùng tạo ra learning penalty cho mọi người khác. Một khi họ đã học xong, họ sẽ thấy làm việc với code ít khó khăn hơn. Vì vậy khó để nhận ra cách simplify code mà bạn đã quen thuộc. Đây là lý do tôi cố gắng để \"the new kid\" critique code trước khi họ bị institutionalized quá nhiều!\n>\n> Rất có thể tác giả trước đã tạo ra mess khổng lồ này từng increment nhỏ, không phải một lần. Vì vậy bạn là người đầu tiên phải cố gắng hiểu tất cả cùng một lúc.\n>\n> Trong class của tôi, tôi mô tả một SQL stored procedure sprawling mà chúng ta xem một ngày, với hàng trăm dòng conditional trong WHERE clause khổng lồ. Ai đó hỏi làm sao ai có thể để nó tệ đến vậy. Tôi nói với họ: \"Khi chỉ có 2 hoặc 3 conditional, thêm một cái nữa không tạo ra khác biệt gì. Đến khi có 20 hoặc 30 conditional, thêm một cái nữa không tạo ra khác biệt gì!\"\n>\n> Không có \"simplifying force\" tác động lên code base ngoài deliberate choice mà bạn đưa ra. Simplifying cần effort, và mọi người thường quá vội vàng.\n>\n> *Cảm ơn [Dan North](https://dannorth.net) vì comment của anh ấy*.\n\nNếu bạn đã internalize mental model của project vào long-term memory, bạn sẽ không trải qua cognitive load cao.\n\n<div align=\"center\">\n  <img src=\"/img/mentalmodelsv15.png\" alt=\"Mental models\" width=\"700\">\n</div>\n\nCàng nhiều unique mental model để học, càng mất nhiều thời gian để developer mới deliver value. Nếu bạn giữ cognitive load thấp, mọi người có thể contribute vào codebase trong vài giờ đầu khi join công ty. Và điều đó không có nghĩa là ta hy sinh chất lượng, hay cho phép pile of mud xuất hiện.\n\n> Các unique mental model đó là gì? Đó là một set rule, thường là mixture của Clean Architecture/Event Driven Architecture/DDD. Đây là interpretation riêng của tác giả về những thứ khiến anh ta hào hứng nhất. Mental model chủ quan của riêng anh ta. **Extraneous cognitive load mà người khác phải internalize.**\n\nKhi bạn onboard người mới vào project, hãy thử đo lường lượng confusion họ có (pair programming có thể giúp). Nếu họ confused hơn ~40 phút liên tục - bạn có thứ cần cải thiện trong code.\n\n## Ví dụ\n> Software system có lẽ là thứ phức tạp nhất và intricate nhất (về số lượng loại part khác nhau) mà nhân loại tạo ra.\n>\n> *Fred Brooks, The Mythical Man-Month*\n\n- Architecture của chúng ta là standard CRUD app architecture, [một Python monolith trên Postgres](https://danluu.com/simple-architectures/)\n- Instagram scale như thế nào tới 14 triệu user với [chỉ 3 engineer](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)\n- Các công ty nơi chúng ta \"woah, những người này [thông minh vãi](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)\" phần lớn đã thất bại\n- Một function wire up toàn bộ hệ thống. Nếu bạn muốn biết hệ thống hoạt động như thế nào - [đọc nó](https://www.infoq.com/presentations/8-lines-code-refactoring)\n- Designing for Understandability: [The Raft Consensus Algorithm](https://www.youtube.com/watch?v=vYp4LYbnnW8)\n\nCác architecture này khá nhàm chán và dễ hiểu. Bất kỳ ai cũng có thể grasp chúng mà không cần nhiều mental effort.\n\n<details>\n    <summary><b>Nguyên tắc coding và kinh nghiệm</b></summary>\n    <div align=\"center\">\n        <img src=\"img/complexity.png\" alt=\"Super simple code\" width=\"500\">\n    </div>\n    <a href=\"https://twitter.com/flaviocopes\">@flaviocopes</a>\n</details>\n\nInvolve junior developer vào architecture review, họ sẽ giúp bạn identify các khu vực mentally demanding.\n\n**Maintaining software rất khó**, mọi thứ break và ta cần mọi bit mental effort mà ta có thể tiết kiệm. Càng ít component trong hệ thống, càng ít issue sẽ có. Debug cũng sẽ ít mentally taxing hơn.\n\n> Debug khó gấp đôi so với viết code ngay từ đầu. Do đó, nếu bạn viết code clever nhất có thể, theo định nghĩa, bạn không đủ thông minh để debug nó.\n>\n> *Brian Kernighan*\n\nNhìn chung, mindset \"Wow, architecture này chắc chắn cảm thấy tốt!\" rất misleading. Đó là cảm giác chủ quan \"tại một thời điểm\", và nó không nói gì về thực tế. Approach tốt hơn nhiều là quan sát hậu quả về lâu dài:\n- Có dễ reproduce và debug issue không? Hay bạn phải nhảy qua call stack hoặc distributed component, cố gắng hiểu mọi thứ trong đầu?\n- Ta có thể thay đổi nhanh không, hay có nhiều unknown unknown, và mọi người sợ touch mọi thứ?\n- Người mới có thể add feature nhanh không? Có unique mental model nào cần học không?\n\nNhững câu hỏi này khó track hơn nhiều, và mọi người thường không thích trả lời chúng trực tiếp. Nhìn vào một vài software system phức tạp nhất thế giới, những thứ đã vượt qua thử thách của thời gian - Unix, Kubernetes, Chrome và Redis (xem comment bên dưới). Bạn sẽ không tìm thấy gì fancy ở đó, phần lớn là nhàm chán, và đó là điều tốt.\n\n## Kết luận\nTưởng tượng một chốc rằng cái mà ta suy ra trong chương thứ hai thực ra không đúng. Nếu vậy, thì kết luận mà ta vừa phủ định, cùng với các kết luận trong chương trước mà ta đã chấp nhận là hợp lệ, có thể không chính xác. `🤯`\n\nBạn cảm thấy nó chứ? Không chỉ bạn phải nhảy khắp bài để lấy nghĩa (shallow module!), mà đoạn văn nói chung khó hiểu. Chúng ta vừa tạo ra unnecessary cognitive load trong đầu bạn. **Đừng làm điều này với đồng nghiệp.**\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Smart author\" width=\"600\">\n</div>\n\nChúng ta nên giảm bất kỳ cognitive load nào ngoài cái vốn có trong công việc chúng ta làm.\n\n---\n[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\n\n<details>\n    <summary><b>Comment</b></summary>\n    <br>\n    <p><strong>Rob Pike</strong> <i>(Unix, Golang)</i><br>Bài viết hay.</p>\n    <p><strong><a href=\"https://x.com/karpathy/status/1872038630405054853\" target=\"_blank\">Andrej Karpathy</a></strong> <i>(ChatGPT, Tesla)</i><br>Bài post hay về software engineering. Có lẽ là quan điểm đúng nhất, được thực hành ít nhất.</p>\n    <p><strong><a href=\"https://x.com/elonmusk/status/1872346903792566655\" target=\"_blank\">Elon Musk</a></strong> <i>(Rocket)</i><br>Đúng.</p>\n    <p><strong><a href=\"https://www.linkedin.com/feed/update/urn:li:activity:7277757844970520576/\" target=\"_blank\">Addy Osmani</a></strong> <i>(Chrome, software system phức tạp nhất thế giới)</i><br>Tôi đã thấy vô số project nơi developer thông minh tạo ra architecture ấn tượng bằng design pattern mới nhất và microservice. Nhưng khi team member mới cố gắng thay đổi, họ mất hàng tuần chỉ để hiểu mọi thứ fit với nhau như thế nào. Cognitive load cao đến nỗi productivity giảm mạnh và bug nhân lên.</p>\n    <p>Mỉa mai? Nhiều pattern gây complexity này được implement với danh nghĩa \"clean code.\"</p>\n    <p>Điều thực sự quan trọng là giảm unnecessary cognitive burden. Đôi khi điều này có nghĩa là ít module sâu hơn thay vì nhiều shallow module. Đôi khi có nghĩa là giữ logic liên quan với nhau thay vì tách thành các function nhỏ.</p>\n    <p>Và đôi khi có nghĩa là chọn solution nhàm chán, straightforward hơn là clever. Code tốt nhất không phải elegant hoặc sophisticated nhất - đó là code mà developer tương lai (bao gồm cả bạn) có thể hiểu nhanh chóng.</p>\n    <p>Bài viết của bạn thực sự cộng hưởng với các challenge chúng tôi đối mặt trong browser development. Bạn hoàn toàn đúng về browser hiện đại là một trong những software system phức tạp nhất. Quản lý complexity đó trong Chromium là challenge liên tục align hoàn hảo với nhiều điểm bạn đưa ra về cognitive load.</p>\n    <p>Một cách chúng tôi cố gắng handle điều này trong Chromium là thông qua component isolation cẩn thận và well-defined interface giữa các subsystem (như rendering, networking, JavaScript execution, v.v.). Tương tự ví dụ deep module của bạn với Unix I/O - chúng tôi aim cho powerful functionality đằng sau interface tương đối đơn giản. Ví dụ, rendering pipeline của chúng tôi handle incredible complexity (layout, compositing, GPU acceleration) nhưng developer có thể tương tác với nó thông qua clear abstraction layer.</p>\n    <p>Các điểm của bạn về tránh unnecessary abstraction cũng rất trúng. Trong browser development, chúng tôi liên tục cân bằng giữa làm codebase approachable cho contributor mới trong khi handle inherent complexity của web standard và compatibility.</p>\n    <p>Đôi khi solution đơn giản nhất là tốt nhất, ngay cả trong complex system.</p>\n    <p><strong><a href=\"https://x.com/antirez\" target=\"_blank\">antirez</a></strong> <i>(Redis)</i><br>Hoàn toàn đồng ý :) Ngoài ra, cái tôi tin là thiếu trong \"A Philosophy of Software Design\" đã đề cập là khái niệm \"design sacrifice\". Tức là, đôi khi bạn sacrifice thứ gì đó và nhận lại simplicity, hoặc performance, hoặc cả hai. Tôi áp dụng ý tưởng này liên tục, nhưng thường không được hiểu.</p>\n    <p>Một ví dụ tốt là việc tôi luôn từ chối có hash item expire. Đây là design sacrifice vì nếu bạn có certain attribute chỉ trong top-level item (các key chính chúng), thiết kế đơn giản hơn, value sẽ chỉ là object. Khi Redis có hash expire, đó là nice feature nhưng yêu cầu (thật vậy) nhiều thay đổi cho nhiều part, tăng complexity.</p>\n    <p>Một ví dụ khác là cái tôi đang làm ngay bây giờ, Vector Set, Redis data type mới. Tôi quyết định rằng Redis sẽ không là source of truth về vector, mà nó chỉ có thể lấy approximate version của chúng, nên tôi có thể làm on-insert normalization, quantization mà không cố gắng retain large float vector trên disk, v.v. Nhiều vector DB không sacrifice việc nhớ cái user đưa vào (full precision vector).</p>\n    <p>Đây chỉ là hai ví dụ random, nhưng tôi áp dụng ý tưởng này ở mọi nơi. Bây giờ vấn đề là: dĩ nhiên người ta phải sacrifice đúng thứ. Thường có 5% feature chiếm very large amount của complexity: đó là thứ tốt để kill :D</p>\n    <p><strong><a href=\"https://working-for-the-future.medium.com/about\" target=\"_blank\">Một developer từ internet</a></strong><br>Bạn sẽ không hire tôi... Tôi bán mình dựa trên track record của các enterprise project đã release.</p>\n    <p>Tôi làm việc với một người có thể nói về design pattern. Tôi không bao giờ có thể nói kiểu đó, mặc dù tôi là một trong số ít có thể hiểu anh ta rõ ràng. Các manager yêu thích anh ta và anh ta có thể dominate bất kỳ development conversation nào. Những người làm việc xung quanh anh ta nói anh ta để lại trail of destruction phía sau. Người ta nói với tôi rằng tôi là người đầu tiên có thể hiểu project của anh ta. Maintainability quan trọng. Tôi quan tâm nhất về TCO (<i>Total Cost of Ownership</i>). Với một vài firm, đó là thứ quan trọng.</p>\n    <p>Tôi đăng nhập Github sau khi không ở đó một thời gian và vì lý do nào đó nó đưa tôi đến một bài trong repository của ai đó có vẻ random. Tôi nghĩ \"cái gì đây\" và gặp chút rắc rối để đến home page, nên tôi đọc nó. Tôi không thực sự register nó vào lúc đó, nhưng nó tuyệt vời. Mọi developer nên đọc nó. Nó phần lớn nói rằng hầu như mọi thứ chúng ta được nói về programming best practice dẫn đến \"cognitive load\" quá mức, có nghĩa là đầu óc của chúng ta bị đá bởi intellectual demand. Tôi đã biết điều này một thời gian, đặc biệt với các demand của cloud, security và DevOps.</p>\n    <p>Tôi cũng thích nó vì nó mô tả practice tôi làm trong hàng thập kỷ, nhưng không bao giờ thừa nhận vì chúng không popular... Tôi viết stuff thực sự phức tạp và cần mọi help tôi có thể nhận được.</p>\n    <p>Cân nhắc, nếu tôi đúng, nó pop up vì các Github folk, những người rất thông minh, nghĩ rằng developer nên thấy nó. Tôi đồng ý.</p>\n    <p><a href=\"https://news.ycombinator.com/item?id=45074248\" target=\"_blank\">Comment trên Hacker News</a> (<a href=\"https://news.ycombinator.com/item?id=42489645\" target=\"_blank\">2</a>)</p>\n</details>\n"
  },
  {
    "path": "README.zh-cn.md",
    "content": "# 认知负荷才是关键\n\n[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) | [Vietnamese](README.vi.md) | [Nepali](README.np.md)\n\n*这是一份持续更新的文档，最后更新：**2025 年 10 月**。欢迎你的贡献！* \n\n*本译文基于原文 [6df913d](https://github.com/zakirullin/cognitive-load/commit/6df913dc1c1bf63dfe5933144155908ce68f6808) 版本进行翻译。*\n\n## 简介(Introduction)\n这世上充斥着各种流行术语与“最佳实践”，但大多数最终都失灵了。究其原因，在于这些理念往往只是想象中的图景，而非源自现实。它们建立在美学偏好与主观判断之上。我们需要更基础、更不可能出错的东西。  \n\n有时我们在阅读代码时会感到困惑。困惑消耗时间和金钱。困惑源于过高的“认知负荷”。它不是某种花哨的抽象概念，而是**人类的一种基本约束。** 它不是臆想出来的，它的确存在，而且我们能真切感受到。  \n\n鉴于我们在阅读与理解代码上所花费的时间远多于书写代码所花费的，我们应当持续地自省：我们是否正在把过多的认知负荷添加到代码中。 \n\n## 认知负荷(Cognitive load)\n> 认知负荷是指开发者为了完成一项任务需要动多少脑子。\n\n阅读代码时，你会把变量的取值、控制流逻辑、调用序列等“装”进脑子里。普通人的工作记忆大约能同时容纳[四个这样的信息块](https://github.com/zakirullin/cognitive-load/issues/16)。一旦认知负荷接近这个阈值，理解就会变得困难得多。\n\n*假设我们被要求去修补一个完全陌生的项目。有人告诉我们，之前有位非常聪明的开发者贡献过：用了很多酷炫的架构、花哨的库、时髦的技术。也就是说，**作者为我们制造了极高的认知负荷。***\n\n<div align=\"center\">\n  <img src=\"/img/cognitiveloadv6.png\" alt=\"随着开发的深入，程序和脑子都变成了一团浆糊\" width=\"750\">\n</div>\n\n我们应该尽可能降低项目中的认知负荷。\n\n<details>\n  <summary><b>认知负荷与打断</b></summary>\n  <div align=\"center\">\n    <img src=\"img/interruption.jpeg\" width=\"480\">\n  </div>\n</details>\n\n> 我们会以一种非正式的方式使用“认知负荷”这个术语；有时它与认知负荷的科学概念一致，但我们并不确切知道在哪些地方一致、哪些地方不一致。\n\n## 认知负荷的类型(Types of cognitive load)\n**内在负荷(Intrinsic)**——由任务本身的固有难度引起。它不可消减，是软件开发的核心所在。  \n\n**外在负荷(Extraneous)**——由信息的呈现方式引入。由与任务不直接相关的因素造成，比如“聪明作者”的各种癖好。它可以被大幅削减。本文将聚焦于这种外在认知负荷。 \n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"如果外在负荷占据了主导地位，情况就不再是喜闻乐见了\" width=\"600\">\n</div>\n\n下面直接看一些外在认知负荷的具体、可操作的例子。\n\n---\n\n我们将这样标注认知负荷的程度：  \n`🧠`：工作记忆刚刚清空，认知负荷为零  \n`🧠++`：工作记忆里已经有两个事实，认知负荷上升  \n`🤯`：认知过载，超过 4 个事实  \n\n> 我们的大脑远比这复杂且尚未被充分理解，但这个简化模型足以用来说明问题。\n\n## 复杂的条件控制(Complex conditionals)\n```go\nif val > someConstant // 🧠+\n    && (condition2 || condition3) // 🧠+++, 前置条件需为 true，c2 或 c3 必须其一为 true\n    && (condition4 && !condition5) { // 🤯, 到这一步我们基本已经被绕晕了\n    ...\n}\n```\n\n用有意义的中间变量重构：\n```go\nisValid = val > someConstant\nisAllowed = condition2 || condition3\nisSecure = condition4 && !condition5 \n// 🧠，无需死记条件表达式，变量名已自描述\nif isValid && isAllowed && isSecure {\n    ...\n}\n```\n\n## 多层嵌套的 if 判断(Nested ifs)\n```go\nif isValid { // 🧠+, 嵌套代码仅适用于有效输入\n    if isSecure { // 🧠++, 仅对有效且安全的输入执行\n        stuff // 🧠+++\n    }\n} \n```\n\n对比一下“提前返回”式的：\n```go\nif !isValid\n    return\n \nif !isSecure\n    return\n\n// 🧠, 不必关心之前的 return；能走到这里就表示一切 OK\n\nstuff // 🧠+\n```\n\n通过这种写法，我们可以专注于主流程，从而使“工作记忆”从各种先决条件中解放出来。\n\n## 多继承噩梦(Inheritance nightmare)\n我们要对管理员用户做一些改动：`🧠`\n\n`AdminController extends UserController extends GuestController extends BaseController`\n\n哦，部分功能在 `BaseController`，看看先：`🧠+`  \n基础角色机制是在 `GuestController` 引入的：`🧠++`  \n一些逻辑在 `UserController` 被部分改写了：`🧠+++`  \n终于来到 `AdminController`，开始干活吧！`🧠++++`  \n\n等等，还有个 `SuperuserController` 继承了 `AdminController`。改 `AdminController` 可能会破坏子类逻辑，所以先去看 `SuperuserController`：`🤯`\n\n尽量倾向组合而非继承。细节不展开——参考资料已经[汗牛充栋](https://www.youtube.com/watch?v=hxGOiiR9ZKg)。\n\n## 过多微型方法，类或模块(Too many small methods, classes or modules)\n> 在这里，“方法”“类”“模块”可以互换理解。\n \n“方法少于 15 行”“类应该小”这些准则，实践下来并不总是对的。\n\n**深模块(Deep module)**——接口简单，但功能强大、实现复杂  \n**浅模块(Shallow module)**——相较其提供的少量功能，对外提供的接口反而比较复杂 \n\n<div align=\"center\">\n  <img src=\"/img/deepmodulev8.png\" alt=\"相较于深度“抽象”的浅模块其内部犬牙交错的调用关系，深模块可能因为链条清晰而让理解与维护成为可能\" width=\"700\">\n</div>\n\n浅模块过多会让项目难以理解。**不仅要记住每个模块的职责，还要记住它们之间的所有交互。** 要理解一个浅模块的目的，往往得先看完与之相关的所有模块功能。频繁在这些浅组件之间跳转令人心力交瘁，<a target=\"_blank\" href=\"https://blog.separateconcerns.com/2023-09-11-linear-code.html\">线性思维</a>对人类更自然。  \n\n> 信息隐藏至关重要；而浅模块隐藏的复杂性并不多。\n\n我有两个宠物项目，都大概 5K 行代码。第一个有 80 个浅类，第二个只有 7 个深类。我一年半没维护它们了。\n\n回头再看，第一个项目中 80 个类之间的交互简直难以理清。在能动手写代码之前，我得先重建一大坨认知负荷。相反，第二个项目很快就能上手，因为它只有少数几个接口简单的深类。\n \n> 最好的组件是功能强大而接口简单的组件。  \n> \n> *John Ousterhout, 《软件设计的哲学》*\n\nUnix I/O 的接口很简单，只有 5 个基本调用：\n```python\nopen(path, flags, permissions)\nread(fd, buffer, count)\nwrite(fd, buffer, count)\nlseek(fd, offset, referencePosition)\nclose(fd)\n```\n\n现代实现可以有**数十万行代码。** 尽管背后是大量复杂性逻辑，但因为接口设计简单，使用就容易。\n\n> 这个深模块的例子来自 John K. Ousterhout 的著作 [《软件设计的哲学》](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)做了极佳的诠释。二者皆为必读。更多延伸阅读：[《〈软件设计的哲学〉与〈代码整洁之道〉》](https://github.com/johnousterhout/aposd-vs-clean-code)、[《可能是时候停止推荐〈代码整洁之道〉了》](https://qntm.org/clean)、[《小型函数的弊端》](https://copyconstruct.medium.com/small-functions-considered-harmful-91035d316c29)。\n\n<details>\n    <summary><b>重要的事物应当有足够的分量，丰富的示例</b></summary>\n    <br>\n    <div align=\"center\">\n        <img src=\"/img/dirty.png\" alt=\"整洁 vs 粗糙\" width=\"600\">\n    </div>\n    <blockquote>如果你允许那些关键的核心函数（“关键点”）写得长一些（“粗糙”一点），反而更容易在茫茫函数之海中将其识别出来，它们的重要性一目了然：看，它们体量庞大，显然不同凡响！</blockquote>\n    这张图取自 <a href=\"https://htmx.org/essays/codin-dirty/\" target=\"_blank\">《Codin' Dirty》</a> 由 Carson Gross编写。你将在这里找到大型函数(deep functions)的<a href=\"https://htmx.org/essays/codin-dirty/#real-world-examples\" target=\"_blank\">真实世界案例</a>。\n</details>\n\nP.S. 如果你以为我们是在为臃肿、职责过多的“万能对象”摇旗呐喊，那你就误解了。\n\n## 关于“仅为一件事负责” (Responsible for one thing)\n我们常常遵循一种模糊的原则，结果造出一堆浅模块：“一个模块应该只负责一件事，而且仅此一件”。但这“一件事”到底是什么？实例化一个对象也算一件事，对吧？那么 [MetricsProviderFactoryFactory](https://minds.md/benji/frameworks) 就理所当然？**这类类名和接口带来的心智负担，往往比它们整个实现还高——这算什么抽象？** 哪儿不对劲了。  \n\n我们修改系统是为满足用户和利益相关方的诉求。我们应当对他们负责。  \n\n> 一个模块应当且只应当对一个用户或利益相关方负责。  \n\n这才是单一职责原则(SRP)的真正内涵。通俗地说，如果我们在一个地方引入了 bug，然后两位不同业务条线的人都来投诉——我们就违反了该原则。它与我们在模块中做了多少事情无关。    \n\n但即便如此，这条规则也可能“利少弊多”。每个人对它的理解都不尽相同。更好的做法是看它们带来了多少认知负荷。记住“一个地方的变更会在不同业务流中引发一串连锁反应”本身就很费脑力。就这样，无须再学什么花哨术语。  \n\n## 过多的“浅微服务” (Too many shallow microservices)\n“浅-深模块”原则与规模无关，同样适用于微服务架构。浅微服务太多，毫无益处——行业正朝着某种“宏服务”发展，即不那么浅(=更深)的服务。最糟且最难修复的现象之一是所谓“分布式单体”，它往往源于过度细粒度、过度浅化的拆分。\n\n我曾为一家初创公司做咨询，五个开发做了 17(！)个微服务。他们比计划落后了 10 个月，离正式发布遥遥无期。每个新需求都需要修改 4+ 个微服务。在这样一个分布式系统里，复现和调试一个问题要花掉极长时间。正式推出的估时和认知负荷都高得令人无法接受。`🤯`  \n\n这是应对新系统不确定性的正确方式吗？在一开始就厘清正确的逻辑边界极其困难。关键是在合理的范围内尽可能晚地做决定，因为那时你手头掌握的信息最多。若一上来就引入网络层，我们的设计决策从一开始就变得难以回退。团队给出的唯一理由是：“FAANG(脸书、亚马逊、苹果、网飞和谷歌)证明了微服务架构是有效的”。*醒醒吧，别再做不切实际的美梦了。*\n\n[Tanenbaum-Torvalds 辩论](https://en.wikipedia.org/wiki/Tanenbaum%E2%80%93Torvalds_debate)曾指出，Linux 的单体设计有缺陷且过时，应改用微内核架构。确实，从“理论与美学”的角度看，微内核似乎更优。然而从实践的角度看——三十年过去了，基于微内核的 GNU Hurd 仍在开发中，而单体结构的 Linux 无处不在。你现在阅读的这个网页由 Linux 伺服，你的智能茶壶都跑在 Linux 上，单体的 Linux。\n\n经过精心设计，由模块良好隔离的单体系统，往往比由众多微服务组成的架构更灵活，维护所需的心智负担也更小。只有当“独立部署”的需求变得至关重要时(比如团队规模扩张)，才应考虑在模块之间加入网络层，逐步演进为未来的微服务。\n\n## 特性丰富的编程语言(Feature-rich languages)\n当喜欢的语言发布新特性(Feature) 时，作为开发者我们往往会很兴奋。我们会学习它们，并在代码里用起来。\n\n如果特性很多，我们可能会花费半小时，通过写几行代码来试试某个功能或者另一个功能。这多少有点浪费时间。但更糟的是，**等你过一阵子再回头看，你还得重新推演一遍当时的思路！**\n \n**你不仅要理解这个复杂的程序，还得搞明白：当时写代码的人，为什么会觉得用这些特性来解决问题是个好办法。** `🤯`\n\n这些话来自 Rob Pike。\n\n> 减少选择，就能降低认知负荷。  \n\n编程语言的特性是很好的，只要这些特性之间互不干扰。 \n\n<details>\n  <summary><b>一位拥有 20 年 C++ 经验的工程师的想法 ⭐️</b></summary>\n  <br>\n  前几天我看我的 RSS 阅读器，发现“C++”标签下居然有三百来篇未读文章。我从去年夏天起就没读过一篇关于 C++ 的文章，而且我觉得我的感觉一如既往的好！<br><br>\n  我用 C++ 已经 20 年了，差不多是我人生的三分之二。我的大部分经验都花在语言的边边角角(各种未定义行为)上。这不是可复用的经验，如今要把它们全都丢下，多少有点奇怪和不安。<br><br>\n  比如你能想象吗，<code>||</code> 在 <code>requires ((!P&lt;T&gt; || !Q&lt;T&gt;))</code> 和 <code>requires (!(P&lt;T&gt; || Q&lt;T&gt;))</code> 中含义不同。前者是约束析取(constraint disjunction)，后者是我们熟悉的逻辑或运算符，而且它们的表现是不同的。<br><br>\n  你不能仅仅为一个平凡类型(trivial type) 分配内存然后直接 <code>memcpy</code> 将字节复制过去——那并不会启动对象的生命周期。这在 C++20 之前是这样的。在C++20的版本里修了，但语言的认知负荷却只增不减。<br><br>\n  即便有些问题被修正了，认知负荷却在不断增加。作为专业的开发者，我需要知道什么被修了、何时修的、修之前是什么样。没错，C++ 对遗留的特性支持很好，这也意味着你<b>得直面</b>那些遗留。例如上个月同事问了我一个 C++03 的行为。<code>🤯</code><br><br>\n  以前有 20 种初始化方式。后来加了统一初始化语法。现在我们有 21 种了。顺带一提，还有谁能记住从初始化列表里选择构造函数的规则吗？好像是跟隐式转换和“信息损失最小”有关，<i>但如果</i>值在编译期是已知的，那就是另一回事了……<code>🤯</code><br><br>\n  <b>这些额外的认知负荷并非来自当前的业务任务，也不是领域的内在复杂性。它只是因为历史原因而存在的</b>(<i>外在负荷</i>)。<br><br>\n  我不得不定些规则。比如，如果某行代码不那么一目了然，我还得去回忆标准，那我最好别那样写。顺便说，标准大概 1500 页。<br><br>\n  <b>我绝不是在指责 C++。</b>我热爱这门语言。只是我累了。<br><br>\n  <p>感谢 <a href=\"https://0xd34df00d.me\" target=\"_blank\">0xd34df00d</a> 的来稿。</p>\n</details>\n\n## 业务逻辑与HTTP状态码(Business logic and HTTP status codes)\n在后端我们返回：  \n`401` 表示 JWT 令牌过期  \n`403` 表示权限不足  \n`418` 表示用户被封禁  \n\n前端的工程师在实现登录功能时，会用到后端 API。于是他们必须在脑子里临时记住这样一组映射：\n`401` 表示 JWT 令牌过期 // `🧠+`，好，先记着  \n`403` 表示权限不足 // `🧠++`  \n`418` 表示用户被封禁 // `🧠+++`  \n\n前端开发者(希望如此)会在代码里放一个“数字状态码 -> 语义”的映射，这样后面加入的贡献者就不用再在脑子里重新构建这套映射关系了。\n\n然后 QA 进场：  \n“嘿，我遇到 `403`，这是令牌过期还是权限不足？”  \n**QA 不能马上开始测试，因为他们得先重建后端工程师当初在脑中构建的认知负荷。**\n\n为什么要把这种自定义映射强行塞进我们的记忆中呢？更好的做法是把业务细节从 HTTP 传输协议中抽离，直接在响应体里返回自描述的代码：\n```json\n{\n    \"code\": \"jwt_has_expired\"\n}\n```\n\n前端侧认知负荷：`🧠`(清空，脑中无须再记映射)  \nQA 侧认知负荷：`🧠`\n\n同样的规则适用于各种“数字类型的状态码”(无论在数据库里还是别处)——**优先使用自描述的字符串。** 我们早就不处在需要为 640K 内存做优化的年代了。\n\n> 人们会为 `401` 和 `403` 争论不休，依据各自的心智模型(mental model) 下判断。新的开发者加入后，他们又得重新推演一遍当时的思路。也许你写了架构决策文档(Architecture Decision Record, ADR)来记录“为什么”要这样做，帮助新人理解当时的决策。但归根结底，这并不明智。我们可以大体把错误分成“用户相关”和“服务器相关”，除此之外，界线其实很模糊。 \n\nP.S. 区分“authentication”和“authorization”本身就令人头大。用更简单的词比如[“登录”(login)和“权限”(permissions)](https://ntietz.com/blog/lets-say-instead-of-auth/)可以降低认知负荷。\n\n## 滥用 DRY 原则(Abusing DRY principle)\n\n“不要重复你自己”(Do not repeat yourself, DRY)——这是作为软件工程师最早学到的原则之一。它深入骨髓，以至于我们难以容忍多出的几行重复代码。尽管它总体上是个良好且基础的准则，但过度使用会带来难以承受的认知负荷。\n\n如今大家都基于逻辑上分离的组件构建软件。它们往往分布在多个代码库中，代表不同的服务。当你致力于消除一切重复时，可能最终会在本不相关的组件之间建立起紧耦合。结果是，一个地方的变更会在其他看似无关的地方产生意想不到的后果。这也会阻碍单独替换或修改某个组件的能力，因为它会牵一发而动全身。`🤯`  \n\n事实上，即使在单个模块内，也会出现同样的问题。你可能会基于一些看起来相似但长远来看根本不存在的共性而会过早地抽取出所谓的“通用功能”。这会导致产生一些不必要的抽象，难以修改或扩展。\n\nRob Pike 说过：\n\n> 适度的复制好过引入新的依赖\n\n我们常常过于抗拒“重复造轮子”，以至于为了使用一个很小的函数，不惜引入庞大而笨重的库。而这些库我们本可以轻松自己写出来。\n\n**你的依赖库，也是你代码的一部分。** Bug 来去无踪，不可预测，当你为了定位问题的根源而不得不翻阅某个依赖库的的十几层调用链时，你会知道什么叫做折磨。\n\n## 框架的紧耦合(Tight coupling with a framework)\n框架里有很多“魔法”(A.K.A. 奇技淫巧)。当我们过度依赖某个框架时，**会迫使后来的开发者先去学习这些魔法。** 这可能要花上几个月。虽然框架能让我们在几天内推出 MVP，但从长远看，它们往往带来不必要的复杂性与认知负荷。\n\n更糟糕的是，当遇到一个与架构格格不入的新需求时，框架可能会成为最大的约束。于是人们开始 fork 框架并维护自有的定制版。想象下新人要积累多少认知负荷(也就是学会这个定制框架)才能产出价值。`🤯`\n\n**当然，我们绝不是提倡从零开始造轮子！！**\n\n我们可以以相对“框架无关”的方式编写代码。业务逻辑不应被放在框架内部；相反，它应该仅使用框架的组件。把框架放在核心逻辑之外，用它像用库(library)一样。这样一来，新加入的贡献者从第一天就能带来价值，而不需要先在框架的复杂细节中摸爬滚打。 \n\n> [《为什么我讨厌“框架”》](https://minds.md/benji/frameworks)\n\n## 分层架构(Layered architecture)\n这些东西，确实能让工程师的肾上腺素飙一飙。\n\n我自己曾经是六边形架构(Hexagonal Architecture) / 洋葱架构(Onion Architecture) 的热情拥护者，持续了好几年。这里用、那里也用，还鼓励其他团队用。随后项目复杂度上去了，单看文件数量就翻了一倍。我们像是在写大量“胶水代码”。在需求持续变更的背景下，我们得在多层抽象里同时改动，一切变得繁琐乏味。`🤯`\n\n**抽象本应隐藏复杂性，但这里它只是增加了[间接层](https://fhur.me/posts/2024/thats-not-an-abstraction)。** 要快速定位问题，理解哪里出错了、缺了什么，通常需要沿着调用链逐步跟踪。但在这种分层架构中，层与层之间的解耦导致我们需要额外的、甚至割裂的多层调用链来找到错误点。每一条这样的调用链，都会占据我们有限的工作记忆。`🤯`\n\n<div align=\"center\">\n  <img src=\"/img/layers.png\" alt=\"Layers\" width=\"400\">\n</div>\n\n这种架构最初看起来很直观，但我们每次在项目中实践，收效都不如预期，得不偿失。我们耗费了数年时间，在那些不必要的心智活动和编写毫无明确业务价值的胶水代码上。不仅如此，我们还强行要求新人必须先学习我们那套“心智模型”，结果反而给业务造成了更多困扰。这最终导致了产品上市时间的进一步延迟。最终我们回归朴素的依赖倒置原则。**不用再学什么端口/适配器，不要非必要的的横向抽象层，不制造外在负荷。**\n\n如果你以为分层能让你快速替换数据库或其他依赖，那就错了。更换存储会带来大量问题；相信我们，数据访问层的抽象是你最不该担心的事情。最理想情况下，抽象也就省个 10% 的迁移时间(如果真能省的话)；真正的痛点在于数据模型不兼容、通信协议、分布式系统挑战，以及[隐式接口](https://www.hyrumslaw.com)。  \n\n> 当 API 的用户足够多时，  \n> 你在契约里承诺了什么已不再重要：  \n> 系统一切可观察到的行为  \n> 都会被某些人所依赖。\n\n我们做过一次存储迁移，而这花了大约 10 个月。老系统是单线程的，因此对外暴露的事件是顺序的。我们的系统都依赖这种可观察到的行为。但这种行为既不在 API 的契约里，也不在代码中体现。新的分布式存储没有这种特性，因此无法保证顺序，事件就会乱序触发。我们只花了几个小时就写好了新的存储适配器(storage adapter)，这的确要归功于抽象化。**接下来的 10 个月，我们都花在处理乱序事件及其他挑战上。** 现在再说“抽象/分层能帮助我们快速更换组件”，有点可笑。\n\n**既然分层架构带来了如此高的认知负荷，而未来可能没有回报，为什么要付出这样的代价？** 况且在大多数情况下，所谓“未来要替换某些核心组件”的场景根本不会发生。  \n\n这些架构本身并非基础性的原则，而是一些主观的、带偏见的实现方式。为什么要依赖这些主观解释？我们应该遵循更基础的规则：依赖倒置原则、单一事实来源(single source of truth)、认知负荷控制、信息隐藏。业务逻辑不应该依赖数据库、UI 或框架等底层模块。我们应该能够在不依赖基础设施的前提下，为核心逻辑编写测试，这就足够了。[参与讨论](https://github.com/zakirullin/cognitive-load/discussions/24)。\n\n不要为了“架构”本身去叠加抽象层。只有在确实需要扩展点、并且有实际理由支撑时，才去增加抽象层。\n\n**[抽象层不是免费的](https://blog.jooq.org/why-you-should-not-implement-layered-architecture)，它们会占用我们有限的工作记忆。**\n\n## 领域驱动设计(Domain-driven design, DDD)\n尽管领域驱动设计（Domain-driven design，缩写为 DDD）常常遭受误解，但其在某些方面的确有卓越之处。人们通常说的 “我们用 DDD 写代码”，这种说法其实是有些奇怪的，因为 DDD 是和问题空间（problem space）相关的，而不是和解决方案空间（solution space）相关的。\n\n> 译注：<br>\n> problem space: 问题空间，简单理解就是当前环境下业务所面临的一系列问题和背后的需求。<br>\n> solution space: 解决方案空间，则是针对问题空间的解决方案，它思考的是如何设计实现软件系统以解决这些问题，它属于工程设计实施阶段，通常是技术专家主导的解决方案设计和实现。\n\n通用语言(ubiquitous language)、领域(domain)、有界上下文(bounded context)、聚合(aggregate)、事件风暴(event storming)——这些都属于问题空间的范畴。它们帮助我们洞悉领域、划定边界。DDD 让开发者、领域专家与业务人员能用一种统一语言高效沟通。然而，相较于关注这些问题空间的方面，我们常常强调特定的文件夹结构、服务(service)、仓库(repository) 以及其他解决方案层面的技术，而忽略了DDD在问题空间上的问题。\n\n问题在于，我们对 DDD 的理解往往是独特且主观的。如果我们在这种理解的基础上去写代码，就会制造大量额外的认知负荷 —— 未来的开发者只能陷入困境。`🤯`  \n\nTeam Topologies 提供了一个更好、更易理解的框架，帮助我们在团队之间分担认知负荷。工程师在学习 Team Topologies 后，往往能形成较为接近的心智模型。相比之下，DDD 往往让 10 个读者形成 10 种不同的理解。它本该是“共同语言”，结果却成了“辩论场”.\n\n## 熟悉的项目中的认知负荷(Cognitive load in familiar projects)\n\n> 问题在于，**熟悉并不等于简单。** 二者给人的*感觉*看似是一样的 —— 都让人不用费太多脑力就能轻松的在代码之间穿梭 —— 但原因完全不同。你使用的每一个看似“聪明”(其实是“自我炫技”)且非大家惯用的技巧，都会让别人付出额外的学习成本。一旦他们学会了，那与代码相处就没那么难了。这就是为什么你很难看出如何简化你已经熟悉的代码。我会尽量让“新来的开发者”在他们还没被环境同化之前来评审代码！  \n>\n> 之前的作者很可能是一点点把代码写乱的，而非一次性造成的。你可能是第一个必须一次性搞清楚整个烂摊子的人。\n>\n> 我在课堂上描述过某天遇到的一个冗长 SQL 存储过程，其巨大的 WHERE 子句里有数百行条件。有人问，怎么会有人让代码变得这么糟。我告诉他们：“当只有两三个条件时，再加一个没什么差别；当有二三十个条件时，再加一个也没什么差别！”  \n>\n> 在代码库里，没有任何“简化的力量”会自动发生。唯一能让代码变简单的，就是你自己做出的有意识的选择。简化需要付出努力，而人们往往无暇顾及。\n>\n> —— 感谢 [Dan North](https://dannorth.net) 的评论。  \n\n如果你已把项目的心智模型(mental model) 内化到长期记忆里，你就不会感到高认知负荷。  \n\n<div align=\"center\">\n  <img src=\"/img/mentalmodelsv15.png\" alt=\"心智模型\" width=\"700\">\n</div>\n\n要学习的独特心智模型越多，新成员产出价值所需的时间就越长。如果你保持较低的认知负荷，就能让新员工在入职几小时内，可以为代码库做出贡献。这并不意味着我们要牺牲代码质量，或允许混乱的代码(Big Ball of Mud)出现。 \n\n> 这些独特的心智模型究竟是什么？它是一套特定的规则体系，通常融合了清晰架构(Clean Architecture)、事件驱动架构(Event Driven Architecture)与领域驱动设计(DDD)的理念。这其实是作者基于自身最受启发的理论所形成的个人化解读，是他主观构建的心智模型。**可这也恰恰成为了他人必须内化的额外认知负担。**\n\n当你为新人做项目的入职培训时，试着衡量他们的困惑程度(结对编程会有帮助)。如果他们连续 ~40 分钟仍很困惑——那你的代码就有改进空间。  \n\n## 示例(Examples)\n> 人类所创造的诸多事物中，软件系统或许是其中最为精密复杂的（若以构成要素的多样性而言）。\n>\n> *Fred Brooks, 《人月神话》*\n\n\n- “我们的架构是标准的 CRUD 应用架构，[一个基于 Postgres 的 Python 单体](https://danluu.com/simple-architectures/)”\n- Instagram 如何在仅[3 名工程师](https://read.engineerscodex.com/p/how-instagram-scaled-to-14-million)的情况下扩展至 1400 万用户\n- 那些让我们惊呼“哇，这些人[聪明绝顶](https://kenkantzer.com/learnings-from-5-years-of-tech-startup-code-audits/)”的公司，大多都失败了\n- 一个函数串起整个系统。如果你想知道系统如何运转——[去读它](https://www.infoq.com/presentations/8-lines-code-refactoring)\n- 为易懂性而设计：[Raft一致性算法](https://www.youtube.com/watch?v=vYp4LYbnnW8)\n\n这些架构相当“无聊”且易懂。任何人无需太多心力就能掌握。  \n\n<details>\n    <summary><b>编码原则与经验</b></summary>\n    <div align=\"center\">\n        <img src=\"img/complexity.png\" alt=\"极简代码\" width=\"500\">\n    </div>\n    <a href=\"https://twitter.com/flaviocopes\">@flaviocopes</a>\n</details>\n\n在架构评审中让初级开发者参与进来，他们能帮你识别出那些对脑力要求很高的部分。\n\n**维护软件很难**，总会出问题，我们需要尽可能节省每一分心力。系统组件越少，问题也就越少。调试也会更省心。  \n\n> 调试的难度是写代码的两倍。因此，如果你把代码写得尽可能“聪明”，那意味着你根本不够聪明去调试它。\n>\n> *Brian Kernighan*\n\n总体而言，抱着 “哇，这个架构真舒服！”这样的心态是有误导性的。那只是“某一时刻”的主观感受，丝毫不能代表现实。更好的方法是长期观察它的后果：\n- 问题是否易于复现和调试？还是你不得不穿梭于多层调用栈或分布式组件之间，在你脑海中试图拼凑出完整的调用链路？\n- 我们能否快速进行修改？还是说，系统中存在大量未知的不确定性，导致大家不敢轻易调整？\n- 新人能否快速添加功能？是否有一些独特的心智模型需要学习？\n\n这些问题很难追踪，人们也往往不愿直接回答。看看世界上最复杂且经得住时间考验的软件系统——Unix、Kubernetes、Chrome 和 Redis(见下方评论)。你不会在它们身上找到什么花哨的东西——大多“很无聊”，这恰恰是好事。\n\n## 结论(Conclusion)\n想象一下，如果我们在第二章里推断出的结论其实并不正确。那意味着，我们刚刚否定掉的结论，以及之前章节里我们认为正确的那些结论，也可能都不成立。`🤯`  \n\n感受到了吗？你不仅需要在文章中来回跳转才能抓住意思(浅模块！)，而且这个段落本就难懂。我们刚刚在你的脑中制造了不必要的认知负荷。**不要把这种事加诸在你的同事身上。**\n\n<div align=\"center\">\n  <img src=\"/img/smartauthorv14thanksmari.png\" alt=\"Smart author\" width=\"600\">\n</div>\n\n我们应当减少一切超出工作本身所带来的认知负荷。 \n\n---\n[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\n\n<details>\n    <summary><b>评论</b></summary>\n    <br>\n    <p><strong>Rob Pike</strong> <i>(Unix, Golang)</i><br>好文章.</p>\n    <p><strong><a href=\"https://x.com/karpathy/status/1872038630405054853\" target=\"_blank\">Andrej Karpathy</a></strong> <i>(ChatGPT, Tesla)</i><br>一篇关于软件工程的好文。也许是最真实、却最少被践行的观点。</p>\n    <p><strong><a href=\"https://x.com/elonmusk/status/1872346903792566655\" target=\"_blank\">Elon Musk</a></strong> <i>(Rockets)</i><br>确实如此。</p>\n    <p><strong><a href=\"https://www.linkedin.com/feed/update/urn:li:activity:7277757844970520576/\" target=\"_blank\">Addy Osmani</a></strong> <i>(Chrome，世界上最复杂的软件系统之一)</i><br>我见过无数项目中，聪明的开发者用最新的设计模式和微服务构建了令人印象深刻的架构。但当新成员尝试做改动时，他们花了好几周也只是试图弄清楚各部分如何拼在一起。认知负荷高到离谱，生产力直线下降，bug 倍增。</p>\n    <p>讽刺的是？许多引入复杂性的模式，都是以“整洁代码”的名义被实施的。</p>\n    <p>真正重要的是减少不必要的认知负担。有时这意味着用更少、更“深”的模块替代许多“浅”的模块。有时这意味着把相关逻辑放在一起，而不是拆成无数微小函数。</p>\n    <p>有时这也意味着选择无聊、直接的方案而非聪明的方案。最好的代码并非最优雅、最复杂的——而是未来的开发者(包括你自己)能很快看懂的代码。</p>\n    <p>你的文章让我非常有共鸣，尤其是对于我们在浏览器开发中面临的挑战。你说现代浏览器是最复杂的软件系统之一，这完全正确。管理 Chromium 的复杂性是一项持续的挑战，也与文中关于认知负荷的许多观点高度契合。</p>\n    <p>我们在 Chromium 中处理复杂性的做法之一，是谨慎的组件隔离和在子系统(如渲染、网络、JavaScript 执行等)之间定义清晰的接口。这类似你关于 Unix I/O 的“深模块”例子——我们追求在相对简单的接口背后提供强大的功能。比如，我们的渲染管线处理着令人难以置信的复杂性(布局、合成、GPU 加速)，但开发者可以通过清晰的抽象层与之交互。</p>\n    <p>你关于避免不必要抽象的观点也很扎心。在浏览器开发中，我们持续在“让代码库对新贡献者更友好”与“处理网络标准和兼容性的内在复杂性”之间做平衡。</p>\n    <p>有时，在复杂系统里，最简单的方案才是最好的。</p>\n    <p><strong><a href=\"https://x.com/antirez\" target=\"_blank\">antirez</a></strong> <i>(Redis)</i><br>完全同意 :) 我认为《A Philosophy of Software Design》里欠缺的一个概念是“设计牺牲”。也就是，有时你牺牲一些东西，换来简单、性能，或两者兼得。我一直都在践行这个理念，但它常常不被理解。</p>\n    <p>一个例子是，我一直拒绝让哈希字段(hash items)支持过期。这是一次“设计牺牲”，因为如果某些属性只存在于顶层条目(键本身)，设计更简单，值就只是对象。当 Redis 引入哈希字段过期时，功能是不错，但确实需要在许多地方做很多改动，复杂性上升。</p>\n    <p>另一个例子是我正在做的 Vector Sets，新的 Redis 数据类型。我决定 Redis 不做向量的“事实来源(source of truth)”，而只是存储近似版本，因此我可以在写入时做归一化、量化，而无需在磁盘上保存大规模的浮点向量等等。很多向量数据库不会牺牲“保留用户放入的全精度向量”这点。</p>\n    <p>这只是两个随机例子，但我到处都在应用这种理念。关键是：当然要牺牲对的东西。往往 5% 的特性就占据了非常大比例的复杂性：那正是该砍掉的 :D</p>\n    <p><strong><a href=\"https://working-for-the-future.medium.com/about\" target=\"_blank\">一位来自互联网的开发者</a></strong><br>你们可能不会雇我……我靠“发过多少企业项目”这类履历吃饭。</p>\n    <p>我曾和一个能“说设计模式”的人共事。我从来不会那样说话，尽管我是少数能很好理解他的人之一。管理者很喜欢他，他能主导任何开发讨论。但周围人说，他走到哪儿都留下一地鸡毛。别人说我是第一个能理解他项目的人。可维护性很重要。我最在乎 TCO (<i>总拥有成本</i>)。对很多公司来说，这才重要。</p>\n    <p>我很久没上 Github 了，某次登录后不知为何跳到某个看起来随机的人仓库里的文章上。我在想“这是什么”，又一时回不了主页，于是就读了。起初没在意，但它太棒了。每个开发者都该读一读。它基本告诉我们：几乎我们被灌输的所有编程最佳实践，都会导致过量的“认知负荷”，意思是我们的脑子被过度的消耗。我早就有这感觉，尤其在云、安全、DevOps 的要求下。</p>\n    <p>我也喜欢它，因为它描述了我几十年来一直在做、但不太好意思承认(因为不流行)的实践……我写的东西非常复杂，需要尽可能多的帮助。</p>\n    <p>想想看，如果我是对的，那它会出现在 Github 首页，是因为 Github 的聪明人觉得开发者应该看看它。我赞同。</p>\n    <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>\n</details>\n"
  }
]