Avoiding Unused Code

We discuss how to avoid unused Elm code, why it matters, and what leads to unused code in the first place.
August 14, 2023


Hello Jeroen.
Hello Dillon.
Do you ever feel used, used and abused?
Or are you the kind of person who
likes to feel appreciated
and you like it when people take
advantage of your skills?
I really do.
I especially like it when they
use me, not abuse me
and they pay me.
That's even better.
That's even better, but...
Maybe you're a little bit like Code then, Jeroen,
because you don't like being unused.
I don't, no.
No, I don't.
It's a pretty good feeling when
your work is appreciated
and... Oh, okay.
Oh, this was a pun
to introduce a topic
of unused things.
It was all a ruse. I actually was just using you.
No, that was abusing me.
Like, definitely.
Not entirely sure of the definition
of that word, but
now I feel abused.
At least feel used.
Well, Jeroen, how
do you make sure that your
code is used
while also avoiding abusing
your code, because we wouldn't want that.
And what are we talking about today?
Well, we're
talking about
techniques, technical
practices that help you avoid unused code.
Unused code?
Do you have thoughts and opinions
on unused code?
Not really.
Maybe I have
a blog post somewhere
or two or
four and
four different
presentations on it.
five packages about unused code.
But no, I wouldn't say I have
any thoughts
on that. Not much.
You've kind of carved out a niche
for yourself with unused code.
Or perhaps with used code, I don't know.
Destroying unused code?
Obliterating unused code?
This is not
what I wanted to do with ElmReview.
I didn't think
unused code would be such a focus.
It just does it
so well that it kind of became it.
It became its trademark
in a kind of way.
Elm is too good at
calling you out when you have unused code.
Because it's not like, oh well it might
get called somewhere by some
magic reflection access.
Nope. It's not used.
Let's go to the next issue.
Also not used. Remove it.
Boom. Gone.
So, maybe before
we dive into unused code,
I wanted to touch on
Abused code?
I'm very curious what the definition of that
would be. Me too.
Where do
we find unused code?
In an Elm app?
In your source code.
Source code is a common offender.
Yeah. I think another
place that we find unused code
within your source code
is in comments.
This is an interesting topic because ElmReview
does not look at these
at the moment. It's possible that it
could, but it would require some heuristics
because for all it knows, it could
not be code.
You mean like trying to find
comments that are
commented out code, right?
I actually worked on a rule for that
I think for ESLint.
And I think the result
was that
it sometimes got things wrong.
Like if you had a comment
that was just plain English
without punctuation
or something, then it looked
like just a function call
or something.
Especially if it didn't start with an uppercase
letter, then it
looked like code.
But that was just a comment. So yeah, as you say
plenty of false positives.
But doable, I guess.
You know what my favorite places
for unused code are?
Tests. I do
like... At that point
it's used, arguably.
I love unused code in
version control. I think that's a great
place to store it.
Remove it
and find it again.
I also think that
it's reasonable to
just put something in
your notes manager.
If you just have some
interesting tidbit and you're like, I'm not sure
I want to lose this. You want to have it for
reference? Just throw it in a note.
That's fine.
I would be concerned if someone was
doing that too much.
But... Yeah, you're thinking
for instance, like utility functions, right?
Like if you need a
find function for lists or something.
Or if it's like some code
that I'm uncomfortable parting
ways with because I'm like,
I think I want to do something with this at
some point. Or better
yet, delete it. It's in
version control. And then maybe
in your notes app, maybe you
have a link to the diff from
that in version control.
Or you... Well, the diff
is in a patch, right?
And patches
or git patches can be
stored as files. Yes.
Git patches can be stored as files or a github
link. Github link to
commit. Yeah, absolutely. So first
of all, so we're going to talk
about how to
avoid unused code
and practices to
minimize that. But
why do we care so
much about unused code?
And avoiding having that?
Because it feels
good to have all those
negative diffs in
github. That's true.
It just
feels good to remove code.
Which is so weird.
Because I know that when you're
a beginner, you write a lot
of code. Often it's your code because
you're working on projects
alone because that's
how your projects at school work.
Because you're exploring it
on your own.
And then if you ever need to remove
code that you wrote,
it feels bad. It feels like, oh, well,
I didn't think about this
well enough in the first place.
Which is true, but also
maybe it was
right for the time.
And yeah, as you grow more experienced, for some
reason, it feels good to delete
code. And I actually
wonder why?
Maybe it's just because
we know that all the code that we
have in a codebase,
we need to maintain it.
And we don't like maintaining it.
So whenever we can delete code,
we're like, okay, well, this is a lot of code
that I don't need to maintain anymore.
I would say that's it.
I think that's definitely the case
for me. And I think also
I think I've talked about this
Linus Torvald's definition of
elegance in coding, and how
he thinks that that's one of the main
skills of an
experienced coder,
is being able to identify
when his definition
of elegance being
identifying when a special
case can be folded in
to a single case. So you take, oh,
there was this case, if this, then that, if this,
then that. And you're like, oh, this is actually just
a special case of the same thing.
So I can actually handle this in the
same code path if I
the way this is happening a little bit.
So I think that
that's a sign of your code
becoming more elegant when you're starting to get
unused code paths. It's also a sign
of your product becoming more elegant
in the same type of way when
you realize this feature
can actually be supported through
a smaller set of
primitives in our application
for the user. Yeah. Like, this case
can handle both of these
edge cases at once
in a nicer
way. Yeah, exactly.
So, you know, yeah, the interface
for creating a
post and creating a comment, maybe
we can reuse some of these things. And that's
intuitive for the user, and it's
intuitive, you know. So, removing
those types of special cases,
we know it's going to make less maintenance burden,
we know that when we're
changing things, we can trust that they're changing
in the same place. It reduces the
cognitive load, because we
don't have to think about what all these different
pieces are doing. Yeah.
And I agree about the part
you said, which is basically
about simplifying the code. The more
you remove unused code, the more
you can notice that
some things are simpler than they
were before, and then
you can simplify them even
more, and then notice new simplifications,
new unused code.
And, yeah, every time
I create a new Elmer's View rule to
find unused code, I'm
hoping that it will create a snowball
effect. Like, just start
somewhere, and then you notice, oh, well, this thing
is not used anymore. Oh, and this is also
not used. Oh, and this, that means that
this is also not used. And then you remove
two whole modules and
a dependency and
lines of code, or
3000, I don't know. It just feels
good. Yeah. Again, because
less maintenance, and the code is
now simpler and more maintainable.
Honestly, there's not much
that is bad about it.
The only
thing I would say is, what
we mentioned before, is like, well,
if you ever want to
recover that code, if you want to
reuse it,
even though you've just deleted it,
well, then it's going to be painful.
Like, we'll talk about it later,
but there are techniques to recover
that, like using Git, using
notes, which in practice, I don't
ever need to use.
Like, in one of my
blog posts about removing
dead code with Elmer's
View, so that's called safe
dead code removal.
Wait, what is it called again?
Safe dead code removal
in a pure functional language.
I talk about
YACNI, so that is, you ain't
going to need it, depending on
pronunciation up to your
regional accent.
You ain't going to need it, or something.
I don't know. This is the
Parisian Dutch variant of
that phrase.
I was going for the Texan accent.
I guess.
Yeah, so
you ain't going to need it is about,
like, you wrote a lot of code
that was used before, or that
you want to use in the future,
and the idea is that
you are not going to need it
in practice.
Because very often,
the cases that you need to support,
that you want to
support with that
now unused code,
are not going to appear again, or
are not going to appear in that shape.
Right, that's a great
distinction. Yeah, so
you think, oh, I'm going to need,
like, I'm building a form
API, and I'm going to have
text inputs, I'm going to have
password inputs,
and that works great, and then
at some point I'm going to think, oh, well,
I'm probably going to have, like, radio button
inputs, so I'm going to add support for
those. And
that's going to be a lot of work, because it's
a new kind of input,
and no one ever uses it.
So, like, well, first of all,
you now have code that you're not entirely
sure that it works.
You can write tests, you can have
end-to-end tests with Cypress,
and that gives you a lot of optimism about
whether it works or not.
But it might have bugs that you
didn't catch. And then at some
point you notice, okay, well,
I finally need it.
And you try to implement it, and you're like,
well, actually, I need a checkbox.
That feels much better here.
Yeah, so
what you wanted to support,
turns out you needed to support it in a different
way than you originally thought.
And now you still have all that
code about supporting radio buttons
that is not necessary, that
has a lot of complexity, and that you need
to maintain. Exactly.
It basically, like, you're
bound to this
design that
wasn't necessarily the optimal
design. It was just this
initial path you went
down. So you're tying yourself
down to every
possible thing. It's like
having to be backwards compatible
to MS-DOS from
And now you can't
change your design to support
all these very sensible things, because
you're tied down to that. And of course,
being backwards
compatible for users
and not breaking their stuff has value.
But being backwards
compatible, basically, with
your old code
that you wrote just in case for this thing
you thought you would need in the future, does not
have value. So you're tying yourself down
to a design
for no value. And so
if you can avoid that in your
workflow, then you can
be much more free in the way that you
design your code and your
One of the reasons why we say that Elm
is so nice to work with
is because it's so maintainable. And it's
so maintainable because it makes it
easy to refactor and to change
your code. But if you're not allowed
to change your code because you want to keep
things as they are
today or as they were yesterday,
well then,
you're limiting yourself
to doing
a better job, basically.
You're putting
weight on your ankles that don't feel
Right. And I think that
it's understandable
why that happens. So maybe let's dig into
how do we get there?
If we agree that
unused code
ties you down in an undesirable
way, why do we get into that state
in the first place? And
what's happening when
on a recent
ElmTown episode,
Wolfgang and Jared were talking
about how it can sometimes
be difficult when you're introducing some
of these Elm review rules for
no unused code.
It can sometimes be difficult to get
your coworkers to buy into
that process because they
say, well, okay, no
unused code, fine, but I'm
going to use this code. So how does
that happen? Right? So
how do you get into that state? How do you end up with
unused code? Why does that happen?
Yes. So I
see at least two
ways. One, you had
code that was used and then
turns out you
don't use it anymore for
several reasons. Like one
is, well, you wrote
code that didn't work
or you wrote code
for a feature that
you removed because it was
prematurely designed.
Like, oh, we're going to need
this feature. Oh, it turns out, no,
none of your users are using it.
Let's remove it. Okay, let's remove it.
Well, then you have dead code, remove it.
And yes, things like that, you
had some code, you wrote things differently
now, and those code
paths are never used anymore. The
other way is that
you are prematurely
introducing code
because you're expecting
them to be used or to be useful
later on. So
again, the example of the form is
I think I'm going to need radio
buttons. Let's add them
now. Let's add the complexity
for that now
so that my life
in a week will be easier
or the life of my
fellow developers who have not looked at
the implementation of this form API
will be easier and they don't have to look
at it. They can just
start working on the form
whether they need radio buttons or not.
But I think they're
going to need it. So yeah, I
see those two reasons
mostly. Yeah.
I think this is actually a bit
more subtle in cases like this.
I'm curious what you think because... Oh, there's plenty
of subtlety. Yeah. Go ahead.
So like, if you have something
that is essentially
an internal package,
like a form package,
or, you know,
a button builder,
and, you know, let's say you have
small, medium, large
or button.withSmall
or something like that. And it's like, well,
medium is never used.
Only small and large, right?
Then the question
is, does that represent
part of our design system
that is useful,
which is not useful
right now because we haven't needed it yet,
but is going to be useful?
And this is just a
logical thing to include?
Or is it, we're actually
prematurely introducing this piece
of the design system? And
I think you can make a case
for either. I think
in some cases there are very clear cases
where it's just like, you know,
okay, we, you know,
we're designing something
that follows a spec. You know,
the Open Graph
tags for SEO tags
for sharing links on
Twitter or
[00:17:50], whatever it may be.
You know.
Can you tell when we're recording?
And so, okay,
Open Graph tags have,
you know, these different variants
that are defined in a specification.
There are small
and large, and there are these four
different things you can define.
And we're just exposing an API
for that, right? So you may end up
with an unused thing
if you publish that in a package.
Like, I sometimes see that when I'm
developing code that
it's like, well, this is unused.
So it's like, well, yeah,
but this is part of the specification, and I
definitely want it there because
I'm just following the spec.
Yeah. Well,
this is like
complex in a way because
now it's not about code
in a way because
you're adapting to a
spec, which makes
a lot of sense in my opinion,
but that spec has
unused things. So you're just
moving where the
unused things are. So now it's not
part of the developers'
job where
they introduced unused code or they
did things prematurely.
It's the designer.
And you could probably say,
well, Yagni
works just as
well for you as a
designer as for me as a developer.
So if you don't
intend to use those things, then
don't design them.
And, yeah, I mean,
Yagni works
here just as well.
You might use it,
you might not use it.
I'm sure that in
some of the design
tools, you have ways to
recover old designs or you can
just put them in a separate file
or a separate design file.
So, yeah, I would probably say
this is mostly an issue about
your design team
and you
and your communication with them.
That said, if you have
a specification
where, for instance, you have a
at your company, you have one design
team or one design
style, rather, for
all the products, and one of
the products is written React, and they use
all the things in the spec.
Right, exactly.
And in your Elm code, you're only using a portion
of it. Well, yeah,
those things are
used just not in this code base.
Exactly, and I would be
inclined to make an exception for those and say,
you know what, this is, yes, it's
unused, but maybe it's
actually just, like, literally
using a string.
I mean, you could definitely argue, like, well,
okay, why
don't you use them in
some tests or in an
Elmbook example or a
whatever, you know, storybook example or
somewhere. Yeah,
I mean, you can also say, like,
well, I have a spec,
but is my implementation correct?
And you can only know that
if you use the code. If you're using it, exactly.
So you could probably just
delete it and recover it later
when you need it or just not
write it, you know.
What you just said, like,
brings me to something that I'm working on right
now, and maybe it will be
released by the time that
this episode airs.
You said,
if you want something to be
marked as used or not reported by
Elm Review, you can write a test for
it or you can add an
example in a style guide
in a storybook
or something like that. And I'm
at the moment working on
the no-unused
exports rule report things
that are only used in those files.
So if you have a function
that you can only find
in test code, for instance,
then it's going to be reported.
So writing a test
for it is not going to save you
anymore. That's
the idea. This is going to be
opt-in because
unfortunately this is going to give some
false positives because
you write test helpers that are in
the production code but to enable tests
like, for instance, for when you have
opaque types. So
you're going to have to annotate them, which is
not great, but
so this is something that you
will be able to opt-in
to. If you want
to go the extra mile of like, we really
don't want unused code,
let's put in some
extra effort into
tagging the test helpers
and stuff like that
as such and the rest can be
removed. And I think
that will be pretty good.
That will be pretty useful. But as
you say, I'm
trying this out
on my project at work and
for instance, well, plenty of
our UI components
are unused.
Like, we have
like, I don't know,
600 or 500
icons that our designer
made. Turns out we only use 200.
Well, at least now we know.
And now we can talk to
our designer and tell them,
hey, these 200
or 300 icons are never
used. Is that on purpose?
Do you think we should remove
them? Maybe that will help you
do less work as a designer.
Exactly. Yeah.
And what's going on in that process?
That there's,
are these icons being designed
that are never used? So is it
that the developers accidentally use
the wrong icon? That's possible.
Or is it that the designers
are designing something for
a bunch of
ideas for features that are
then never actually shipped
and then the features that get shipped
have icons that are used. So
I agree with you that you've got to think
about the
process, not just the coding process
but the design process.
Yeah, and definitely there needs to be some
communication to know
what is the expectation?
Is this icon actually supposed to be used?
And if so, why are we not
using it? And that's a conversation you
want to have.
That's the same thing with developers.
So for instance, when I go to a
new part of our
code base at work or
a project that I don't know about
and I start removing code, I'm like
or simplifying it or
changing it, I'm always like
is this how
things are supposed to be?
Was there a reason why we made
this function or why we
wrote code this way?
And often I
feel a bit stuck because
I feel like I don't have the whole
story and maybe I should just remove it
and talk to them afterwards
or before.
At some point it would be nice if I could
talk to them and
figure out what the best way
forward is. So that's
one of the reasons why
there's the ElmReviewSuppress
feature where you
can just say well all these errors that I have
for this or that
rule or all the rules,
let's just ignore them for now.
Which I think of
as identifying
technical debt in a way.
Because now you see, okay
well we have 200 suppressed errors.
Well those are
200 technical debts
mentions, reports.
And let's figure them out at
some point through
grant work or
communication with other developers
or designers.
And there is a cost.
This process you're describing,
you do kind of have to have those
conversations because it's hard to know the intention.
Was this
at the time I thought this would come in
handy down the road, but it turns out
it's fine we can delete it now. Or it's like, well
I thought it would come in handy down the
road. I ended up not needing it.
But we're going to need to check if anybody
else decided to use it and decided
to depend on it. So that's
one of the illustrations
of why it is very costly to have
unused code.
extra complexity. Because
are people
starting to depend on
these things? Are they starting to
build their process in a way where they're
expecting these things
to be sitting around?
As you said, if I
introduce unused code
and then someone else
wants to clean up or wants
to change some kind of behavior,
well they need to go through me.
If that code was not there,
well we wouldn't need to have communication.
So it is
some kind of depth, technical or
or team.
Exactly. And a lot of it does come down
to the process I think. So let's talk about
that. So when I think about
unused code, the
big thing on my mind,
and we've talked about some more subtle corner cases
having something that actually represents
part of a spec that you're
going to use. There are
other subtle cases.
The unsubtle case
that is at the front of my mind
when I'm thinking about unused code
is a process
for building code that
is thinking about things
you're going to need later and
building out code to try
and make it efficient and saying
well while I'm in here
I'm going to write this code.
I'm not using it yet. I might not
use it for a while, but while I'm thinking
about this I'm going to go ahead and write it.
I might not use it for a while, but I'm going to
write it. And I think
this is
what's going on with this type of process
is it's
juggling unfinished work. And there's
a big cost to that because
if code is unused it is
unfinished, right? Like you haven't
shipped it anywhere. So it's
work in progress.
But if you wrote tests
for it you could also say well
this is the exact moment where
you should write that code because
you're in the zone or
you have all the things in your mind
and you're never going to have a better
time to do this in a
way. Because you don't have to relearn
it now because you have everything
in your mind. If you do this, if
you add this feature in a week
then you're going to have to do that.
That said, that is basically
our job. Go to
some part of the codebase and add
new things. So
we should be able to do this easily
anyway. Right, and if you're working in that
way then you might end
up introducing a lot of things
that you think you'll need later
that, I mean we're
talking about unused top
level definitions and unused
custom type variants and the sort of things
that Elm Review can identify but
what about an unused
code path, right? Like a dead code path
is something that
at the moment Elm Review can
only identify if it's a
degenerate case or it's like
if false. Then Elm Review Simplify
can identify that.
There's some that we
also can do. Right.
Or and false and things like that, right?
No, no, like if you have
if a and then
under that you have if a again
then it knows that's going to be true always.
Right, and that's awesome
but that's still pretty
narrow. It doesn't know what
values will actually be flowing through the system
and it may be that
you're never going to get this type of value.
So that's another type of unused code.
So when you're introducing
this is an even more
costly type of
unused code to maintain because
you don't even know where it is
and you look at this code and you're like
wait a minute, is this even possible in our codebase?
And now you have to have that conversation
and you need to try to figure out
is it used? So
that's even more costly to introduce that type of
unused code. But it's
this mindset of
I'll introduce these things
that I think I'll need down the road
as you introduce these things
it seems efficient at the time
but you're bogging down the process
by tying yourself
to these decisions
that aren't serving
you. So it's a balancing
act. It might seem like it's efficient
because you have context
on it now and you're working on it now
but you have to remember that
you're going to now be looking
at that code, asking yourself that question
a few months from now and
if you have that
way of working, that process
now you're going to be incurring that
tax over and over again.
So it's a difficult
mindset shift but I think that
there is another way. There is
a way to write code just in time
and it's a skill
that requires practice
and it's actually
not even a skill, it's a set of skills.
So it's easy to say
well, this is just more efficient
and it might be more efficient
with the skills
you have in your tool belt at the moment
but you have to step back
and say well
what if I picked up
a set of skills to help me with that?
Because often that's the answer.
Actually, another kind of step back
before we go into
the skills that you want to describe
like as people who write
APIs for people to use
often we spend
months or years
perfecting them
and therefore we know
we pretty much never
have the
perfect thing on the first draft
especially not when your mind
is in the zone because you don't
have any... you're not taking
a step back which is really useful
and that means that you're going to miss things
you're not going to see the full picture
and you're not going to revisit
ideas in a novel way
which is really useful
and helps so much with polishing
designs. And in practice
when you work with ElmCode
you often just go
through your code and you're like oh this is not how you
should write your code, this is how you
should do it. So let me rename
this function, let me change the arguments
and so on. And that's what we do
all day every day.
And yeah
if you are doing everything right now
and not when it's necessary
then you're not going to have
that step back, I don't know how
you'd say that. That allows you
to find a better solution.
Right. So, skill sets.
Right, yeah.
And it is a mindset shift of
trusting your future self
a little bit, you know. You have
to trust that you can...
Well I guess you can also
say like if you're doing the job
if you're working as a team on
something and you know that
part of the codebase better
and all the invariants
need to hold up and all the things that
need to be... how things
need to work, then you don't
want to push that work onto
another colleague of yours.
But again like
there are better ways out there like if you
want to have maintain invariants, you can
use update types and so on and so
on. There are techniques to
help you and your team
do what is necessary.
So unless your
colleague is a
beginner, I think
it's usually okay to just
wait. Right. So to me a lot of
these concepts come down to like
the idea of lean. Lean being
the idea of reducing
the cycle
time to actually
complete something and not
partially complete something. Actually
so until a user
has the feature in their
hands, it's not actually used.
And so lean has a
lot of concepts around this.
One of the concepts is
eyes for waste. Like being able
to perceive
where waste is coming from.
So if you're
some unused features,
being able to have
that sense that this is
costing me. This is
incurring a tax every
time I'm working on this code.
There's a cognitive load here, there's a maintenance burden,
there's a code review burden,
there's a
burden to writing
features to these constraints which might
not be necessary. So
that's one thing. And also
you want things to be
you want the feedback loop
to be as short as possible.
If you're creating unused code
you hinted at this earlier. You're
writing unused code, you don't actually
know if it works because you've never used it.
So that is a
real design smell.
You can have a pretty high
degree of confidence
but even with that
you might have bugs.
And if there's anything
I don't know, maybe
we have in common as Elm developers
it's a certain humility
about our own abilities.
We're like, we don't know better than
the compiler.
We want the compiler
to constrain us because we don't trust ourselves
because that's a type of
humility. We're like, no
compiler, help give
me sane constraints
and check things for me because I know
I can't trust myself because I'm just a human
and I make mistakes all the time.
Yeah, that's interesting.
I mean, I absolutely
agree and now I'm thinking
is that the reason why
plenty of people don't trust type systems?
Is because they have such a
high ego?
Maybe that's part of the reason.
Or maybe they play with other type systems.
Yeah, to me
I mean, this is, I don't know.
I think we have a lot of cognitive biases
and I think we have a lot of
reason to not trust ourselves
and I think that
using external tools as much as possible
to not have to trust ourselves
to me is an amazing thing. I love
working that way.
So having half-written code
that's never used
I don't trust it at all.
I don't trust it until I've
written some automated tests for it
and tried it out.
So if I have
half-written code
it's not running in production, it's not running
in a test, then
I'm just accruing all this code that
I have no idea if it works.
So I want to get
things to the definition of
done, whatever that means, as fast
as possible. And so it's really
a question of
I think a lot of it comes down to
do you want to work in horizontal slices
or vertical slices?
A lot of the time it feels more productive
to work in horizontal slices.
So basically a vertical
slice being
you can use it.
You have all the pieces, so like
do you have the front end, the back end
all these
pieces fitting together
or do you have, okay
I wrote the front end for this thing, now I just
need the API and well
in order to write the API endpoint
we're going to need some
warehouse step
that's doing this thing so we need to
ask this other team to do that
and a vertical slice
is just like, alright let's
build the simplest form of
this thing holistically that we can.
What's the simplest
back end processing
work we can do, what's the simplest
API we can write to get some
meaningful, valuable thing.
So we're doing everything but only
as much as necessary in the
sense that we're only going to do it
for this feature, we're not going to do
this other feature that we're thinking of
doing right after this. Exactly.
Yeah, so when you say like
oh I'm just going to keep this
simple and do a really small piece of work
what does that mean to you?
Does it mean I'm going to keep this really
simple and I'm going to
make this
I'm only going to do the
magic link authentication to start
because yes we
want to eventually add
OAuth providers for these
different services and we want to
do password authentication
and we want to do these different things but we also
want to do magic link authentication
and we can have only
that and then we don't need to build the
forgot password flow.
So it's just a way of
solving the puzzle in a way
where you can do one isolated
piece first. So it requires this mindset
and thought process where you're
you develop that skill
where you're able to prioritize things
based on how
independently doable they
are and how you can build
other things on top of them
and also
it being valuable because you're like well maybe
if we build magic authentication
magic link authentication maybe we
don't need password based authentication
can we get away with that?
authentication isn't the most interesting example
but just doing this
thought process for your product
in general.
So that was vertical
slicing, what is horizontal?
Well it would be
I'm just going to build
the front end for this and when the back end team is ready
they'll ping me and let
me know the API is ready and then
I'll merge this pull request
And in the meantime I'm going to
mock the back end call or something
Yeah, mock it or
avoid calling the back end
avoid calling it, I'll just write the UI
for it and nothing's
going to be wired up but I'm going to write the UI
I'm going to get the designs for it
and it's just like to me that
I'm very uncomfortable in that
state of partial completeness
because I know that
there are risks
what if the pieces don't fit together?
There are costs to fitting the pieces together later
just losing
context. I may be building
things that I don't need
I may not be building things that
I do need. To me it's just
I'm very uncomfortable
if I'm spending a long time
building things that can't go to production
for a long time. I don't like
that feeling. I like to just be able to say
this is done, people can
use this, it was a simple thing, we can build
on top of it
I mean another type of
horizontal slicing
would be just writing
like I'm going to write a function, I think
I'm going to need this for this feature so I'm just going to
go ahead and write this code
Because you're not connecting it to
whatever is going to use it
or you're not invoking it
where you need it to
I'm going to go write
an internationalization system
and write all these
I'm going to get all these translations and things
and when I'm done building
that, then I'm going to integrate
it into the code. It's like well
wait, but how is it
going to be the right design for the code
that we have? How do you know that unless you
use it? So start
it's this mindset of
just in case versus
just in time. Build it just in time
and also like using
it as soon as possible. In fact
to me, the best
time to use it is before
it exists. Like
wishing something into
existence. Some IDEs have
create from usage
I love using create from usage
Do a test case
Write your test, red green
refactor, you write the thing you
wish it would do. One simple
example of that
I expect this function call
to give me this result. Error
this function doesn't exist
create from usage, error
through an exception
debug.todo called from this thing
fill in the thing with the
literal value. So it's
this process of like
pulling it in
in Lean they call this push
versus pull. Push being I think
I'm going to need this. So I'm going to
set all these things up that I think I'm going to
need later and pull being
hey once you need something, then you
grab it. So like in
the origins of this concept in
Lean or in manufacturing, car
manufacturing. So in a
car manufacturing scenario
they'll literally have
like boxes
with door handles that
you know when a box
becomes empty, it just
that triggers a new shipment. So you
you don't forecast how many
door handles you need. You
get an empty box and then you ship
them. Now Toyota and these
Japanese manufacturers that
pioneered Lean
concepts needed to actually
work with their suppliers
because if their suppliers
weren't operating in
a Lean way, they would often go in and
literally consult with their suppliers
the door
handle manufacturer to make
sure that they could operate in a
more Lean way. Meaning instead of saying
we're just building door handles
so just make them as fast as you possibly
can, instead saying
how short can we make it between getting
an order and fulfilling that order
and they would work
with their suppliers and make
sure that process was as streamlined as possible
so working
in this way, it
requires changes along
the way. Like we hinted at
it might require you to
have a conversation with your design
team about hey, maybe
can we try to
focus on the features we're building
so we don't end up
having to implement all these things
in our front end
for our design system that we don't yet need.
Can we focus on what are we
building this week?
I'll sit down with you and talk
about these things and talk about
maybe some issues with the design that
I don't know if I can really implement
it the way you designed it here. Can we
talk through alternatives? Because
if they just go and
design a hundred screens
maybe there's an issue in them
and you didn't get the chance to talk about
them until they had gone through all these
meetings with product people and
talked about the perfect design and now they throw
it your way and you're like oh no, that doesn't work.
So it's creating waste.
It requires changes throughout
the entire process. You have to look at it
holistically in order to
like, we're talking about
reducing unused code but you really
do have to make that change holistically
in your process.
I really, really like the vertical
slicing. Because as you said
if you have done a
nice slice, you can
ship it. You can merge it to master, you can
send it to users. There's nothing
that prevents you
from shipping it.
And from marking that
whole section of the code base as done.
You might want to improve it.
You might want to add more features.
But you can ship it and
that means you can avoid having a
very long-standing
branch in your
git repo which
creates git conflicts
which is painful to everyone.
I'm sure we've seen that.
We've all seen that.
When you try to have a branch that is
two months old and like, yeah...
It creates problems.
But yeah, so vertical slicing
if you can go for that
that sounds great.
Horizontal slicing, I think
you can make it work or at least I feel
like you can do that
best when
trying to do a vertical slice.
So when you do a vertical slice
let's say you need a front end and you need
a back end. Well, you can do
the front end
as a horizontal slice
and the back end as a horizontal slice
of that vertical slice.
And that's like
if you don't want to ship that code because
it's not ready yet, you can
use other techniques like
feature flags.
To me, feature flags are
an essential part of
shipping small vertical slices.
If you want to
actually deploy
code, because it is
a product and a
management decision
to release something to go
live in production.
Sometimes people talk about
deployment versus release.
It's a product and a
management decision to release something
but deploying is a technical decision.
Right. So for instance, we want
v2 of our new
product to be released
But we want to be...
Yeah, it's somewhat
similar but we want it
to be ready. We want to test it
on some computers or
some servers maybe. But we
want to only make it available to
some of the users at this time.
We have a big conference
and we want to go
live with this new feature on the day of the
conference. But do you want
to hit the deploy button
on the day of the conference
and make sure everything is ready and fix any
bugs that come up? Or do you want
to ship it with a feature flag and
people are not doing
merge conflicts the day of
the conference when you hit deploy
because the code has been sitting around on a branch
and not shipped
to production? Or do you
want to have people working
with the code change that
was shipped to the production
so you don't have any
merge conflicts but it's behind
a feature flag that's switched off except
for admin users or whatever.
this is one of the core practices
for working this way.
But you ship it
as soon as you possibly
making a new
settings page
and you
ship it right away. You make the settings
page, boom, it's live.
Does it do anything? Well, it doesn't matter
because it's only accessible if you have this feature
flag on. So it's safe to ship.
So it requires that mindset.
Even if it's absolutely empty.
Right. Now you would
probably want to have
something meaningful
of like, what's
the simplest setting?
This is for changing your
username. Okay, well, can
we ship it without any
validations but have that
without any
UI validations, only back
end validations, right? So that's like a shippable
thing that someone can use
and test out and
we can loop back around to like fancy
UI for the front end
validations later, for example.
So this is all like
the concept of trunk-based development
and continuous delivery and
it's, I mean, it's a lot.
And it does require the whole
organization to sort of be on board
with this process.
Especially just the fact that
you want to have like a continuous
deployment. That often
comes with that approach,
right, where you merge something to master.
Well, and everyone can merge
something to master, even
very tiny things. Sometimes
without supervision because you're pairing
with other people. And then
anything that gets shipped to master gets
continually deployed
to production.
And yeah, in plenty of organizations
if that is not automated,
that's not going to work
out. Or it's going to be
subpar, I'm guessing.
Absolutely. And so you
have to be committed to
making all these changes that enable
you to work in this way. So I definitely
don't say it lightly. It's a
huge mindset shift.
But I think this core
mindset shift of seeing the waste
in merge
conflicts, seeing the inefficiency
in these ways and
realizing that actually if I
can ship one small thing, it's not
inefficient. It's actually more efficient.
I can think of this one small
slice of the functionality and
get it tested and
working. And then
trusting yourself to loop back around
and add the additional features
later. And maybe you don't even need the additional features.
So it gives the engineers
and the product team the ability
to do that 80-20
and say, you know what, this actually is good enough
for now. Let's prioritize another feature
first. So seeing the
efficiency of that workflow
I think is a really good first step.
When you say have an
eye for waste, you basically see
you basically mean
trying to find all the
wasteful processes
or where you lose time,
right? So you mentioned
merge conflicts, which makes
a lot of sense because
they can be painful.
One of the things you
need for this is you need to have a good
test suite. You need to have
a good compiler, you need
to have linter that checks that
everything that goes into master
works as expected. But you can also
argue, I guess, and I would
like your take on this,
is that if
Elm Review tells you, oh,
this can't be shipped to production
because you have an unused function,
isn't that kind of a waste
in a way?
Right, well, that's why I say
eyes for waste is
it's understanding
the types of waste that come
from working in this
big batch way. Is it just
a matter of trade-offs? Like we want
to have a
high quality and we're going to
we're willing to pay this
price in terms of
slowing you down
to be able to have that high quality of code.
We don't
want you to be able to ship something
where a test has gone red
because that's going to impact the
behavior of the code. And we're willing to
slow you down because of that.
It's a very good question.
Let's say you're making
let's say you have a factory
that makes rubber bands.
This single size
of rubber band, there's like a constant
demand for it. We can forecast
the demand for rubber bands
each year. It doesn't change that much.
You mean the demand for
rubber bands is constant, not elastic?
It's not elastic, exactly.
So, you don't want to
optimize for flexibility
although perhaps a rubber band
manufacturer would want to
optimize for flexibility. I'm not sure.
To some extent.
It's baked
into their process.
Yeah, it has to be
at some point or another.
So, it might be
a bit of a stretch, but
the rubber band
manufacturer, let's just
imagine they have a
pretty good grasp
on how much demand there is.
They know how much they want to produce per year.
They know their process.
They're not trying to adapt it every day.
They're not trying to create
the next electric car
breakthrough that's going
to transform the industry.
They're not trying to create an innovative
competitive software
product that is going to stay ahead
of the competition and be the
best in class. They're trying to create
rubber bands. Yeah, exactly.
They're not doing those things, right? Yeah, yeah, okay.
Okay, so for this rubber band
manufacturer, maybe
they're okay with optimizing
for horizontal slicing.
Maybe they know exactly how many they're going
to create, so they don't need a flexible
process. They don't need an adaptable
process. So, to me,
that's the question.
How adaptable do you want to be?
Maybe if you are a
web agency
creating photography
websites for professional
photographers, and they can
have a portal to share things
with their clients or whatever. It's like
you stamp it up. You're making wedding
websites. You're making
mom and pop
restaurant websites. Okay.
Maybe in that case
you know exactly what the process
is and you're just repeating it every time.
So, to
me, this lean eyes for
to me, it's not so much a
trade-off of the kinds of waste
of doing everything big batch
and doing everything in small lean
batches. It's a question
of optimizing for flexibility or not.
So, eyes for
waste in lean is understanding
that you're making yourself
less flexible. And if you want to
optimize for something other than flexibility,
then you might make a different
set of choices. That's how I see it.
Am I correct or incorrect to say that
lean is a process that
is optimized for incoming
changes? Yes.
Okay. I would 100%
agree with that. Lean, you don't want
a lot of unsold
cars. You want
a lot of cars that
a lot is in like a parking lot, like a
car showroom
lot that has
cars that are
getting sold. And then it's like,
oh, they're selling really well. Great. Let's
make some more. Let's ramp up
production on that instead of
we thought these cars were going to
sell really well, but that's waste.
That's like a bunch of cars that didn't sell.
Lean is all about
that's the origins of lean.
But these principles very much apply.
And it's all about looking
at how you shorten the
time from starting
to finishing something and actually
finishing, not partially finishing.
It's a holistic
concept of done.
All right. Now, I will mention
we've talked about this before.
If you want to have some code
that is partially done,
another place where
partially done
code can happen is prototyping.
I think that doing small
spikes, small experiments
is a great idea, right?
But the purpose
of a spike
is to experiment in order to learn
something, not to
have some deliverable code.
So you would never, or
almost never, include that into
production code. So you would never merge that into
master, even not behind a feature
flag, or maybe behind a feature flag?
I would think of it as
you throw it away at the end.
And the purpose is
as quickly as we can get to that
learning goal, we're going to optimize for
that all the way. I guess you could
have a spike where you're
trying a test on some production code
for performance, and you say, hey, if we
do this performance change,
is it going to
be faster? And maybe
it's, oh, you know what?
I actually experimented with a bunch of things.
I made this one change, and
actually it's good, so we can merge this.
But the general idea,
you're optimizing for learning.
That's the gist of it.
Or you can ship something, like the
new settings page, to one of your customers
that really wants this
new thing, and
you send it to production,
they validate it, or they say,
no, there's something wrong with this, it's not what we
wanted, actually.
And then you can delete that, and then
you do it properly. Absolutely, yeah.
Right. And I wouldn't call that a spike.
I would just call that
a partial rollout. But yeah,
absolutely. Why would you call
that a spike? Because you're
merging it? It's more
like A-B testing,
or user testing,
or... I mean, of course
you could do
tests as well, without
rolling out a feature change there.
To me, a spike is more just like,
we're not necessarily trying
to tweak the product
and learn how users
respond to it. I just
categorize that under more A-B
testing type of stuff, I guess.
Yeah, then it's basically the same
idea, but then put different words on it.
Yeah. But it's definitely
challenging stuff,
and there's also, like, in TDD,
there's a whole different
set of schools of thought between
outside-in TDD and inside-out,
where outside-in
is more of a pull-based
model, and inside-out is kind of more of a
push-based model. And people
have different opinions on this, but
inside-out TDD is
more the traditional standard
approach we're used to.
So if you're, you know,
if you're building out some tests,
and maybe you're,
you know, maybe you're not using
them yet, whereas inside-out would be more,
okay, I'm writing
an end-to-end test. I'm
using this thing. Now
I need to drop into a more
low-level unit test for
all of these corner cases.
So now I'm gonna start
writing these smaller unit
tests, but I'm gonna start with
the top-level
thing. But yeah, I think, I mean,
if you're using
TDD, that in itself is
really gonna help you avoid
unused code quite a lot. I mean,
this, you're talking about this
rule you're building, this
Elm Review rule that lets you detect
something that is only used in test
code. So, and
that is definitely, like,
yeah, if something is only used in test code,
then it's like, is it really used
code? Not really, right? So...
Yeah, in this case, like,
do you want to maintain it or probably
not? Right, exactly. Yeah, you probably want to know
about it. You probably want to know, hey, there's this thing that's
being tested, but it's not being used anywhere else.
I know that a lot
of people have written tests
for functions that they wanted to keep
just so that Elm Review doesn't report
them, which is
in a way a good thing because they're
running tests. Yeah.
But if that's
just a way to
escape Elm Review, then just
know that I'm coming for you.
Whatever means
you're trying to come up with
where Elm Review doesn't
detect unused code, I'm
coming for you.
Don't rely on it
for a long time. I'm gonna
try to improve it in any way,
shape or possible.
So, for instance,
right now you can have
additional record fields
in your records
because Elm Review is not
collecting them or reporting them.
But at some point, I
know that's gonna come at some point.
And that's gonna
create so many snowball effects, or I hope
so at least. So Elm Review
definitely, when
you came up with the
no unused custom type constructor
rule, that
definitely caught a lot
of things in my code that I was like, wow.
And often it's, I mean, sometimes
it's a premature thing that
I didn't even realize I was doing.
That I'm just creating a variant
and I'm modeling
my, I'm domain modeling.
And I do kind of like
this process, but
sometimes I'll even comment out,
like sometimes I'll do the domain modeling,
I'll write a custom type, and then I'll
comment out some of the ones that I'm not
using. And I think
that's okay.
It's not really tying me up too
much. And I usually try to
tidy up those commented
unused variants
as I'm
committing. If I realize
that I don't need them anymore.
But I keep it almost
like a checklist for myself. And I kind of
like that process.
I think you're writing a to-do list for you.
Except you're writing it in your
code. And I think that's fine
as long as you have,
as long as you're sure that you're going
to go back to that
and clean things up when everything's
done or everything works as expected.
Or implement those things,
obviously. Right. You know, the
other way you could do that is just
write your custom type and start
writing all your cases. But I think
it's a brilliant way to write
code that you say, I'm just going to have
this one case for now. And
I'm going to do a case expression
that only has a single
pattern in it to start.
And then I'm going to write
a failing test. And I'm going
to need a new variant to implement
that and get it green.
And then, guess what?
The Elm compiler is going to tell you all the places you
need to go handle. Which is an amazing
workflow. But I think starting it out
as a custom type is
a great device.
As opposed to just a raw
primitive type or a record. Because it's like, well, I know.
It would be nice if there were automated
tools for like, taking
a record type you have somewhere and then
saying, I actually want this record to
be wrapped in a custom type now. That would be super
cool. Yeah, that'd be really
cool. Yeah. And doable
as well. Yeah.
Automated refactoring tools really
do... I mean, we're talking about
Elm review rules.
And automated refactoring
tools, automated testing tools, all these
things are really essential
for this type of process, I think.
It allows you to be way more flexible
in the way you approach your code workflow because
the cost of change is lower.
Elm also makes the cost of change lower because
it's very predictable, maintainable code.
Yeah. Anything that helps you
make changes to your code
in predictable ways is always
an improvement. Exactly. It's optimizing
for flexibility, right? So, in a
way, Elm is very good for helping you optimize
for flexibility as a language.
So, anything else people should look at
besides all of the great Elm Review
no unused rules?
I think we're good.
Just a reminder that
if Elm Review is blocking you from
doing what you really want,
you can always use Elm Review suppress
and then communicate
with your teammates to know whether that
suppression was okay or not.
And then making sure
yourself, like in your
internal process or team process,
go through the suppressed errors
because that is now technical
debt that has been somewhat
identified. But it is okay
to have some technical debt
as long as you remove it
later in the future. So, that is
also one way you can do
things. But if you can avoid
having unused code
in the first place using whatever
we just... whatever technique
we just mentioned during this
episode, then that's probably
a better solution.
And as we've mentioned before,
if you are able to remove
all these suppressed cases, then
that's a whole new guarantee you have for your codebase.
Including, you know, a guarantee
that you don't have unused functions.
That's kind of cool. You're browsing through your codebase
and you know if a function
is being
declared, it's used.
Thankfully, even if you suppress
that, like
just because Elm has that code elimination
or live code inclusion,
the cost of keeping that code
is pretty much only
in terms of maintenance,
not bundle size.
Well, for unused functions at least,
for unused custom types, there is an impact.
Right. Or for
dead code paths.
Dead code paths, yeah. Also,
so, run ElmReview,
the unused package, the simplified package,
and all the other
ones that I made, I'm sure they're all great
and applicable everywhere.
Not biased.
Share your lines of
deleted code screenshots from your
pull requests and let us know how
it goes. Yep. Always
looking forward to hearing more about those.
And Jeroen, until
next time. Until next time.