Skip to content

Instantly share code, notes, and snippets.

@andriytyurnikov
Last active September 15, 2021 14:39
Show Gist options
  • Save andriytyurnikov/bf159ce31db84d57077f55a10e01f6cc to your computer and use it in GitHub Desktop.
Save andriytyurnikov/bf159ce31db84d57077f55a10e01f6cc to your computer and use it in GitHub Desktop.
Design Systems as single source of truth: common failures.
Design Systems as single source of truth: common failures.
TLDR:
Names should be practically unambiguous.
Namespacing should be based on rules - you may change them, but have them explicitly stated,
so if you reshape your namespaces - only reason is that you gained new understanding of priorities.
Source code repositories should have one 'main' branch for reason. You've been warned.
Dependency structure should be discoverable. Dependency roots are more important than edge.
Naming things is (*may eventually* become) hard.
First of all - this statement is counterintuitive - why naming is hard?
Especially in the beginning of whatever project, when field is green, naming seems easy.
Strictly speaking machine-oriented naming may be extremely simple - Computer Science got very good
in generating strict unique identifiers.
But from perspective of human naming is hard because:
1) set of meaningful unique labels is kinda limited, due to limitations of human memory and language.
2) time changes everything, including meaning of the words.
3) it might be not clear what consitutes "full" name - context often "leaks" into naming:
there is a "button" component, which may be implemented as HTML tags "a", "input", "submit" or ... "button",
so when people say "button" or anything else - what they mean may depend on context, and that context may include:
product, project, use case, role of the person, technology, runtime environment, repository, branch, filesystem location, namespace, time ...
This rabbit hole is bottomless, so unless you love complexity, rule of the thumb is simple:
make naming explicit, and only allow minimal (harmless) influence of context.
If two different things become a part of single conversation - they should have different names:
you have Prefixes, Numeric Versions, Namespaces at your disposal, but it isrecomeded to keep one label to one thing.
This very rule of the thumb would be used later to justify very simplistic usage scenarios of versioning systems.
Time.
Things change with time, meaning of labels and names change, but unless you are in the field of
physics - dealing with time may be relatively easy, as unlike physicists you have a luxury of
dealing with time as like it is linear - going from past right into the future.
Version control systems (git) and release versioning conventions like Semantic Versioning (SemVer)
are quite well understood and commonly accepted approaches to deal with time and change.
However git can do more then linear history, and SemVer may seem not powerfull enough,
which creates temptation to use branching in more sophisticated way.
While everyone is free to choose their poison, I would argue that using branches for something else
than history tracking might be a bad idea.
Conceptually speaking, branches are used as a temporary
divergence vessel which is created to deal with change in a controlled manner.
Common usage of branches is straightforward - branches are destined to converge back into 'main' branch,
to die off with time, or to get a new life as a new thing with it's distinct name (or even it's own repository).
Sure one might brake those rules - but such experiments would come with a cost of being misunderstood.
People use branches as a directories, namespaces and while strictly speaking it is a *valid* usage,
but do you really want your naming to be dependent on so many factors?
Make yourself a favor - don't play complex games - use branches as temporary container of change
- small local divergence from linear model of time - branch is a little secret between you,
QA guys and repository - others don't have to know about it.
Dependencies.
Good side of medal - "single source of truth", bad side of medal - "single point of failure".
Make no mistake - when at scale - benefits of centralisation and tight integration come with great cost,
especially when you'll have to deal with reality of many people dealing with same codebase,
without even knowing each other.
I won't even pretend that I know the answer to the puzzle of dependency management, but here are few hints:
Let's define some terminology: Root of dependency tree - is what many depend on, edge of dependency tree - are those who depend on roots.
1) Configurability is half naming, half managing complexity.
Maybe
<Button size="large"
width="expand"
icon={path_to_icon}
text={translate('sales.call-to-action')}
on-click={performAction()} />
is a good design, but maybe not.
When configuration parameters are independent - you may have one test set per parameter (or per value),
and only question is do you really need all of those. 5 tests - easy.
But when parameters only relevant in very limited use cases, yet they start influencing each other...
<Button
size="large"
width="expand"
icon={path_to_icon}
enable-on-click-animation={true}
html-container="a" /> <!-- Yay, flexibility! -->
Ok, here, we introduced 'one more thing', as browsers may display a button via different HTML tags(input, submit, button)
and usually people use few others to simulate button appearance (a, div), and while it sounds like you've added some flexibility,
(and you did), but you see, browsers do treat different HTML elements differently, so you better test sizing,
alignment and animations for each of those optionns, also, now you have different constraints on what may be placed inside of your button,
depending on the type of the HTML container - make sure you cover those edge cases too.
Is it worth it? Your choice. Remember, you have an option of saying "No" and having separate element instead:
<!-- Use with caution, we'll see how it would go. -->
<ExperimentalButton
3d-animation={true}
html-container="a"
size="large" />
2) Namespaces are your friends: namespaces allow to organize elements by Business Context (Sales/Button),
by function (Layout/Sidebar), by lifecycle (Experimental/SuperButton),
by development team (TeamRed/SearchBar), by degree of dependencies (Common/Button)
- namespaces are about co-existance, co-evolution and rules for entering/leaving namespace.
Namespace is a kingdom - define explicit namespace rules when creating one -
you'll change them if they won't work, but those changes should be visible.
3) Dependency roots should be less dependent themselves, "owned" by less people, be relatively stable (low frequency of changes)
and have better test and documentation coverage.
4) Names are scarse, but change and maintainance ain't free either - sometimes it is just a time to split a thing into two (or more).
Good signs of need of new entity (name): different consumers, different use cases, different authors, different lifecycles, different dependency trees.
To manage dependencies one should be able to see them,
and to reason about their cost/value - so make cost/value visible or at least discoverable.
Mess is a structure which is created without awareness.
Good luck.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment