Software Architecture Trade-offs: Simple Interfaces vs Small Functions Debate

I’ve been thinking about two different approaches to writing code after working with some tricky UI components lately.

On one side, there’s the idea from A Philosophy of Software Design that says we should create modules with simple interfaces that hide all the messy details inside. The user doesn’t need to know how things work internally.

On the other side, Clean Code tells us to break everything into tiny, focused functions. This often means more functions to deal with, which can feel like the opposite approach.

I ran into this issue recently with a menu component library. The component had way too many options and parts:

import { MenuSystem } from "ui-toolkit";

export default () => (
  <MenuSystem.Container>
    <MenuSystem.Button />
    
    <MenuSystem.Overlay>
      <MenuSystem.Panel>
        <MenuSystem.Header />
        <MenuSystem.Option />
        
        <MenuSystem.Section>
          <MenuSystem.Option />
        </MenuSystem.Section>
        
        <MenuSystem.Toggle>
          <MenuSystem.Check />
        </MenuSystem.Toggle>
        
        <MenuSystem.Divider />
        <MenuSystem.Pointer />
      </MenuSystem.Panel>
    </MenuSystem.Overlay>
  </MenuSystem.Container>
);

Compare that to something much simpler:

<ActionMenu
  title="User Settings"
  items={[
    { url: '/profile', text: "Edit Profile" },
    { url: '/security', text: "Change Password" },
    { url: '/remove', text: "Remove Account" },
  ]}
/>

The first approach gives you tons of control but takes forever to learn. The second one is way easier to use but less flexible.

How do you decide when to go with simple interfaces versus breaking things into smaller pieces? What’s your strategy for making this choice?

The decision often comes down to who your primary users are and how they consume your code. After years of building internal tools versus public APIs, I’ve noticed that external developers almost always prefer the simpler interface approach. They want to accomplish tasks quickly without learning your entire system architecture. However, when building for experienced team members who understand the domain deeply, the granular approach can be more valuable because they need that fine-grained control. I’ve found success using a hybrid strategy where the simple interface handles 80% of common use cases, but you can still access the underlying granular components when needed. This way you get the best of both worlds without forcing users into either extreme.

hmm this is interesting - have you considered what happens when your team grows? like, do new developers pick up the simple interface faster or do they get frustrated when they need something that’s not exposed? also curious about maintenence - which approach ends up being more painful to update later on?

depends on your use case tbh. i usually go with the simple interface first and then expose more granular control only when i actualy need it. like start with the ActionMenu example and add a ‘renderCustomItem’ prop or something when basic items arent enough. saves you from over-engineering upfront