<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>emre şahin's digital garden 🍃 - Software Architecture</title>
    <link>https://emresahin.net/categories/software-architecture/</link>
    <description>Posts in the Software Architecture category</description>
    <language>en</language>
    <managingEditor>contact@emresahin.net (Emre Şahin)</managingEditor>
    <lastBuildDate>Tue, 29 Sep 2026 14:57:43 +0000</lastBuildDate>
    <atom:link href="https://emresahin.net/categories/software-architecture/rss.xml" rel="self" type="application/rss+xml"/>
    <item>
      <title>Software architecture as a tree</title>
      <published>2022-05-12T04:33:08+00:00</published>
      <updated>2022-05-12T04:33:08+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Thu, 12 May 2022 04:33:08 +0000</pubDate>
      <link>https://emresahin.net/Execution-and-memory-as-a-tree/</link>
      <guid isPermaLink="true">https://emresahin.net/Execution-and-memory-as-a-tree/</guid>
      <description>In Rust, there are several ways to manage memory. Memory has two regions: One is called the stack . Each new scope (between { and } ) adds variables to the stack. Variables on the stack are allocated at once when a new scope is introduced, and they are freed when the scope ends. This brings speed...</description>
      <category>Software Architecture</category>
      <category>Memory Management</category>
      <category>Rust</category>
      <category>Memory Management</category>
      <category>Stack</category>
      <category>Heap</category>
      <category>Tree</category>
      <category>Concurrency</category>
      <category>Software Design</category>
      <content:encoded><![CDATA[<p>In Rust, there are several ways to manage memory. Memory has two regions:
One is called the <em>stack</em>. Each new scope (between <code>{</code> and <code>}</code>) adds variables to
the stack. Variables on the stack are allocated at once when a new scope is
introduced, and they are freed when the scope ends. This brings speed.
Variables on the stack are also allocated contiguously, so they are <em>more
compatible</em> with the principle of locality.</p>
<pre><code>{
  var1
  var2
  {
    var3
    var4
  }
}
</code></pre>
<p>The inner scope variables can’t be accessed by the outer scope, but outer
scope variables can be accessed by the inner scope. When we are in the inner
scope, the stack contains both scopes’ variables. Stack-based memory
management can be called table d’hôte. It’s faster and simpler.</p>
<p>However, in order to allocate them at once, variable sizes must be known
beforehand. If the compiler doesn’t know the size of an array, it can’t
allocate the array on the stack. We need another method to use these <em>indefinite</em>
variables.</p>
<p>The other way of obtaining memory is using the <em>heap</em>. The heap is independent of
the scope. You can allocate and fill it when you need with a size determined
at runtime, share it with other scopes, and free it à la carte. Rust
provides several different ways to manage memory on the heap.</p>
<p>In my experience, making the program flow as scoped as possible without cross-cutting
memory management is a good sign. Ideally, a program should have a tree-like
flow of execution, and the number of heap allocations is minimized in this
case. However, most modern software systems are not designed this way, and
memory management is not considered during the requirements phase. When you
leave this to a garbage collector, even if it does its best, memory management often
remains haphazard.</p>
<p>Ideally, when you run the program, it should call a series of functions that
branch into further sub-functions:</p>
<pre class="mermaid">
flowchart LR

  main---&gt;fn1
  main---&gt;fn2
  main---&gt;fn3
  main---&gt;fn4
  fn1---&gt;fn1.1
  fn1---&gt;fn1.2
  fn1---&gt;fn1.3
  fn1---&gt;fn1.4
  fn2---&gt;fn2.1
  fn2---&gt;fn2.2
  fn2---&gt;fn2.3
  fn2---&gt;fn2.4

  fn1.1--&gt;fn1.1.1
  fn1.1--&gt;fn1.1.2
  fn1.1--&gt;fn1.1.3
  fn1.1--&gt;fn1.1.4
  fn1.1--&gt;fn1.1.5


</pre>

<p>If we adhere to this principle, building concurrent software becomes a lot
easier, too. If the functionality and memory of <code>fn1.1.1</code> and <code>fn2</code> are
completely separate, these can be run in parallel. Hence, our aim in
<em>software architecture</em> must be looking for ways to represent the problem at
hand as a tree structure, both physically (for memory) and temporally
(for execution).</p>]]></content:encoded>
    </item>
    <item>
      <title>Object-Oriented Brain Damage</title>
      <published>2022-04-05T06:10:16+00:00</published>
      <updated>2022-04-05T06:10:16+00:00</updated>
      <author>Emre Şahin</author>
      <pubDate>Tue, 05 Apr 2022 06:10:16 +0000</pubDate>
      <link>https://emresahin.net/Object-Oriented-Brain-Damage/</link>
      <guid isPermaLink="true">https://emresahin.net/Object-Oriented-Brain-Damage/</guid>
      <description>So, this is the post that I’ve been thinking about for some time. I’m still surprised that it feels like swearing in church ; it shouldn’t be this hard (and this rare) to criticize object-oriented programming . What do I mean by OOP? Mainly the classical style, where you define classes with membe...</description>
      <category>Software Architecture</category>
      <category>Programming Paradigms</category>
      <category>OOP</category>
      <category>Object-Oriented Programming</category>
      <category>ORM</category>
      <category>Principle of Locality</category>
      <category>Python</category>
      <category>Performance</category>
      <category>Software Engineering</category>
      <content:encoded><![CDATA[<p>So, this is the post that I’ve been thinking about for some time. I’m still surprised that
it feels like <em>swearing in church</em>; it shouldn’t be this hard (and this rare)
to criticize <em>object-oriented programming</em>.</p>
<p>What do I mean by OOP? Mainly the <em>classical</em> style, where you define classes
with members and methods and use them to model the solution you’re working on.
It was supposed that this way was superior to the <em>procedural style</em>, where you
write procedures that modify global state. I believe the rage
against <em>procedural programming</em> was because of this <em>global state</em>.
Somehow, in all domains, we have to maintain state, and having a global
state makes the reusability of procedures/functions almost impossible.</p>
<p>I can understand how the reaction against <em>global state</em> led to something like
Java’s “everything is a class” creed. In order to contain global state, you
need classes that contain parts of it and interact through methods. The idea is
simple: if we forbid global state and keep partial state within <em>structs</em> (or classes),
and mutate it with methods, we get reusable software. Once upon a time, I believed
in this, too.</p>
<p>In an ideal world where we could write software in Smalltalk instead of
C++, I’d probably not write this post. Actually, this post isn’t about C++ or
Java, either. They have their places; they solve real problems and
were probably necessary steps to arrive at Go or Rust. We now
have better ideas thanks to the “everything should be a class” worldview.</p>
<p>The problem, in my experience, is applying these <em>classical</em> classes to
interpreted languages. In C++, although it became like a Leviathan with
arms for many different paradigms, there is some care for the <em>cost of
abstraction.</em> If you know the tool, you can get away with classes. Java also
seems to try to make <em>abstractions</em> cost zero. So if you use these languages,
it’s a matter of modeling, habit, or domain suitability. I still think, over
the long term, OOP increases the maintenance burden, but like most ideas in
our profession, this is not rigorously tested.</p>
<p>However, when it comes to <em>multi-paradigm</em> interpreted languages like Python,
<em>objects</em> begin to hurt. The problem, in my opinion, is that <em>object-oriented</em> design
is in conflict with the Principle of Locality.</p>
<p>The Principle of Locality (PoL) is probably the most important
<em>empirical</em> idea in software design. It’s directly related to the Pareto
Principle, Zipf’s Law, and other well-known notions. Our CPUs, disks, search
engines, and content delivery networks depend on it to cache artifacts for
reuse. We know it works because a CPU with a larger L1 cache is faster; we add
more caches to CPUs and disks to increase their speed.</p>
<p>The conflict between the PoL and OOP arises when classes include data
that is not directly related to the <em>problem at hand.</em> The problem at hand, for
example, might be finding the maximum of one million numbers or performing transformations
on some variables. But if these are members of a class, they bring unusable data
to the <em>locality.</em> If the variable I’m transforming is a member of a class, when
I access it through the object, all other members—be they 3, 30, or 300—are
also referenced. Thus, looping over objects ends up polluting the <em>locality</em>
with all other members of the class.</p>
<p>In compiled languages, the advantage of iterators over plain loops is to
overcome this. However, in interpreted languages, no one writes specific
iterators for their member variables. This means when I have:</p>
<pre><code class="language-python">
for obj in my_objects:
    obj.do_something(a)

</code></pre>
<p>All the members of <code>obj</code> are now in the loop. This is against the PoL.</p>
<p>Another problem I see with OOP is its <em>alienation</em> from the basic
elements of software. When you begin to work with objects that do not directly
correspond to anything in computer/software architecture, it becomes more or
less a castle in the clouds. You have to tie this castle—i.e., the <em>class
hierarchy</em>—somehow to the ground. The ground is the CPU, memory, disk, cache, etc.
Compilers may do a good job of this, or they may not, but this
detachment from the basic elements of computing machinery is the cause of what
I call <em>Object-Oriented Brain Damage.</em></p>
<p>When you begin to believe that the <em>objects</em> in your program are <em>real</em>, you
try to express your problem using more objects. Classes are like kipple. They
proliferate all the time. You add classes, then you add classes to create
classes, then you add some base class to derive classes, then you try to fix
these problems with patterns, and so on.</p>
<p>But this whole enterprise doesn’t have anything to do with the <em>real tools</em> we
have. Our tools are processors, memory, disks, screens, printers, etc. When
we detach the problem from these basics, it doesn’t become <em>more solvable.</em> We
only create an ideal version of our understanding that requires <em>additional</em>
attention to teach others, to document, etc.</p>
<p>It’s true that we need abstractions over the tools—we cannot just <em>read
bytes from disk</em>, <em>process in CPU</em>, and <em>print to screen</em>. These all
must be abstracted. But in my experience, OOP is not a good way to abstract
these tools. Instead, it tries to abstract the problem at hand in an arbitrary
way that is <em>supposed</em> to solve the problem. Yet this solution itself becomes a
problem that must be fit onto the <em>ground.</em></p>
<p>Databases and ORMs are good examples of this. Databases and
Entity-Relationship theory are well-understood abstractions. We know how to use
them across multiple CPUs, multiple disks, and multiple machines across multiple
continents. ORMs are not like this; they are <em>supposed</em> to correspond to
databases and provide that <em>cozy object-oriented feeling</em>. However, any system
that depends on ORMs learns that these are not <em>identical</em> to databases. They
don’t have the same capabilities and performance, and over the long run,
depending on an ORM causes more problems than just writing SQL
queries. Because an ORM does not consider the tools we have and their
limitations, it tries to fit an <em>idealistic</em> world view onto databases. When it
doesn’t scale, we think this <em>performance degradation</em> is <em>natural.</em></p>
<p>No, it’s not natural. When your models load whole rows from billions of
records just to access a single field for a calculation, you deplete the cache
space quickly, and it doesn’t scale. OOP might just be an <em>educational</em> tool,
but even in this regard, I believe it causes most of the brain damage we see in
enterprise software.</p>]]></content:encoded>
    </item>
  </channel>
</rss>
