spotifyovercastrssapple-podcasts

Incremental Steps

We talk about the value of taking small steps, and techniques to help break problems down in Elm.
October 19, 2020
#15

Transcript

[00:00:00]
Hello Jeroen. Hello Dillon. How are you doing today? I'm doing quite well, how about you?
[00:00:06]
Doing good and today we're talking about one of my favorite topics and hopefully something that
[00:00:12]
people are practically bored of hearing me talk about. If that's the case then I've done my job.
[00:00:17]
So what are we going to talk about? Today we're talking about incremental steps. So this is really
[00:00:27]
a way of life in my mind. It's been something that I have been very influenced by this way of thinking,
[00:00:35]
working in tiny steps and I just find it so much nicer and I find that I'm so much more productive
[00:00:42]
when I use tiny steps. So I'm looking forward to diving into this topic. Yeah, we met last year at
[00:00:47]
Europe and you were doing a talk on this subject exactly. Yes. So I know you as this guy. Oh nice,
[00:00:55]
that's not a bad thing to be known as. I like it. It's definitely on brand for me. That and ElmGraphQL
[00:01:01]
all the time. Yeah. The more I know about you, the more it's incremental steps and that Elm pages
[00:01:10]
you can't stop talking about. That's true. That's true. The JAMstack has been a fun one too. But
[00:01:17]
yeah, incremental steps are huge if you will. Pardon the... Yeah, I had to. Okay, so can you
[00:01:25]
can explain what tiny steps are? Yeah, so to me a tiny step is a way of breaking down a larger problem
[00:01:36]
into a small action that makes visible progress, not theoretical progress but concrete progress.
[00:01:45]
So I think of it like a pulley that you know, I can't lift a car. That's actually not really true.
[00:01:58]
I actually can lift a car very easily if I use a pulley. If I have a pulley and I spread that work,
[00:02:05]
that force I need to apply over a large enough area, then I can very easily lift a car, right?
[00:02:11]
And to me, that's what the power of tiny steps are is I can take a problem that's too difficult
[00:02:17]
for me to solve, just flat out, okay, go solve the code. Go lift that car. Lift that car. I can't do
[00:02:26]
it. But if I break it down and stretch it over a larger area doing many, many tiny steps, then I
[00:02:32]
can do it with ease. So to me, that's what a tiny step is. Okay. So what does that look like in Elm
[00:02:40]
code for instance, or in programming in general? There are so many forms it can take. I think one
[00:02:47]
of the clearest forms of this idea is this concept in test driven development of fake it till you make
[00:02:53]
it. But as I sort of showed in that Elm Europe talk, you can apply fake it till you make it,
[00:02:59]
even if you're not doing test driven code. So like, I mean, there are many cases when I wouldn't,
[00:03:06]
for example, test drive, like writing my view code, like, I mean, you can use Elm program test
[00:03:12]
to do that sort of thing that I generally don't test drive my view code, because I sort of just
[00:03:18]
change it until it looks like I want it to. And that's, that's, that's usually fine for me. I
[00:03:24]
usually instead try to test the logic behind the view code, because that's the interesting part and
[00:03:29]
separate those out. But so like, you know, like I showed in that Elm Europe talk, without actually
[00:03:35]
writing tests, you can apply that same principle from test driven development of fake it till you
[00:03:40]
make it to to write your view with all the right values, that the values are completely faked out.
[00:03:46]
So you haven't done any logic to drive those values, but you know, they're the values you'd
[00:03:50]
like to have. So it's sort of like wishful thinking that you wish you had this interface to grab this
[00:03:57]
data, you make it so by just wishing it into existence, and faking it till you make it and, and
[00:04:05]
that allows so in that process, you have done nothing, right? You've done no logic, no difficult
[00:04:13]
work, but you've also done everything because you've completely wired it in, you've got working
[00:04:18]
code, you can look at it in the browser, you can make a change and see it reload. So you've done
[00:04:24]
none of the hard work. And yet you've done all of the sort of wiring and all of those things. So you
[00:04:31]
don't trip up on those details. And you have a point to iterate on.
[00:04:35]
Gotcha. Well, when you say fake it until you make it, you do need something that tells you whether
[00:04:40]
you made it or not. So in yes, in test driven developments, that is when the test the tests are
[00:04:47]
green. And when you what you showed in that Elm Europe talk is that you were using the compiler,
[00:04:54]
if the compiler tells you that it's that everything is okay, then you've made it.
[00:04:59]
Right? Yes, exactly. You need some sort of feedback loop. And I think in that talk, I was also using
[00:05:07]
live reloading as a type of feedback loop. So we sort of had the view, presenting the information
[00:05:13]
we wanted, we weren't actually looking at the JSON value to start. And then we started actually
[00:05:20]
pulling out that JSON value. So anything so there are many ways to, to break things down into
[00:05:26]
smaller problems like that. And I just find like, you can think of it as a sort of putting all the
[00:05:33]
risk upfront. Because you, you want to find out, if you're going in the wrong direction, you want
[00:05:39]
to find out earlier. And so taking smaller steps allows you your the goal is to get to working code
[00:05:46]
as soon as possible. And so there are a number of like techniques that then make this easier. Like
[00:05:52]
one technique is, you know, if you're processing a list, maybe you can have an empty list, or maybe
[00:05:58]
you can have a single item in that list, right?
[00:06:01]
Yeah, and it could be hard coded.
[00:06:03]
Exactly, you can hard code a value. I do this often with like, when I'm working on Elm parser stuff,
[00:06:10]
like Elm Markdown, I will start with, hmm, I really wish that this part of the parser that I'm
[00:06:17]
working on, you know, like if it's a GitHub flavored Markdown table, actually, we did a we did a live
[00:06:24]
stream where we built that initial code for that feature. And we used a lot of these techniques. So
[00:06:30]
you, you know, you say, Well, I wish that I had this data structure coming back from this parser.
[00:06:36]
When I go over this GitHub flavored Markdown table, I want to get this data so I can turn it into,
[00:06:43]
you know, the rendered Markdown, you know, the the destination, the goal, and you just sort of
[00:06:48]
short circuit getting there as fast as you can, by passing through a fake value. And then you've,
[00:06:53]
you've wired things through all the way. So like, you can't get feedback until you've wired things
[00:07:00]
through. So the goal is to have as short of a window of time, where you've cut off your feedback,
[00:07:08]
you want to be in a constant feedback cycle.
[00:07:10]
Yeah, the main idea I get from tiny steps is shorter feedback loops, the shorter, the quickest
[00:07:18]
you get to your feedback, the happier you will be, because you will be able to check whether your
[00:07:23]
assumptions are right, whether things are possible this way, you will notice problems early on, just
[00:07:30]
like when you're doing startup, like you do an MVP, but the smaller your MVP, the easier, the
[00:07:38]
quicker you will get your feedback. And exactly, the better you will be able to be flexible around
[00:07:46]
your choices and everything.
[00:07:48]
Yes, exactly. I completely agree. I think it's the exact same principles at play with, you know,
[00:07:56]
that sort of MVP thinking, it's just a way of shortening the feedback loop. And like, to me,
[00:08:01]
these, these are the physics of the software world, or maybe maybe not even just the software
[00:08:07]
world. If you, if you don't have feedback, then you don't know which direction to go. And so you
[00:08:12]
can, you know, I mean, it's like, if you're, if you're lost in the woods, and you just start
[00:08:18]
walking, you don't know if you're making progress, you could be getting more lost by walking.
[00:08:23]
Yeah.
[00:08:24]
But if you're lost in the woods, and you know that you need to get west somehow, and you have a
[00:08:31]
compass, then, you know, you might not go in the perfect route, but you at least have a sense of
[00:08:38]
whether you're going in the right direction. And that's what feedback loops give you, they give
[00:08:43]
you, it just feels like you're going blind, if you don't have a feedback loop, when you when you
[00:08:49]
build this habit and get used to working with tight feedback loops, then you feel totally blind
[00:08:55]
if you don't set them up. So like, the first thing that you want to do is set up a feedback loop,
[00:09:01]
because you just don't have the patience to like, go in the wrong direction. Like one place that I
[00:09:06]
see these short feedback loops at play is with when I'm helping people with Elm GraphQL questions.
[00:09:14]
The first thing I think of if somebody's like, I can't get these types to line up, which happens
[00:09:19]
often, right? I mean, it's, yeah, it can be overwhelming, like you're dealing with a lot of
[00:09:24]
different types, you have the, you have some phantom types, and you have to piece all these
[00:09:29]
things together. And, and people can get really stuck on just like a big compiler error, right?
[00:09:37]
And whenever I get a question, when somebody's in that situation, I always start with, how can we
[00:09:45]
break this down into a smaller step so you can get feedback? And actually, sometimes in those
[00:09:51]
situations, it's better to just revert to the last working state. Oh, yeah, yeah, exactly. Like, if
[00:09:58]
there's one thing that I've learned from this whole tiny step that you've shown me, like, if I
[00:10:05]
notice that I'm doing something that is too big, like, well, okay, the problem with doing things
[00:10:11]
that are too big, is that you have plenty of things in your head. And you need to do plenty of
[00:10:18]
things like, I need to add a message, I need to handle it in the update, I need to handle it in
[00:10:24]
the view, to call it in the view. And if you try to do that all at once, while doing a refactor,
[00:10:32]
yes, exactly, while doing a model change, like, you will get lost. At the end, you, the compiler
[00:10:39]
will help you out and all. So you will get there. But it will be much longer, and sometimes more,
[00:10:47]
way more confusing than if you did one thing at a time. If you did, yes, the refactor of the model,
[00:10:53]
as a one step, yes, you make it a compile, you make the test pass and everything. Then you add
[00:11:00]
that button, but it doesn't do anything that compiles the test pass, and so on and so on. Then
[00:11:09]
you do the message, the update, and so on. And every time that I find myself doing too many things
[00:11:16]
at once, often I will just delete what I just did, like, okay, revert and start one thing at a time.
[00:11:24]
And that is one reason why I try to commit more and more often now, so that can easily,
[00:11:31]
can revert back way easier. Exactly. Yeah, because when you get something that you know is working
[00:11:38]
and something that is almost working mixed together, then suddenly everything is effectively
[00:11:44]
not working. Yeah, yeah. But if you know something is working and you commit it,
[00:11:49]
then you have a point to fall back to if things go wrong. I use the Git gutters, the little sidebar
[00:11:56]
with the colors for the Git diffs in my editor all the time. I very heavily rely on that to
[00:12:02]
understand, like, okay, what are the things that I am changing? What could be causing a problem?
[00:12:08]
Because I know I was in a working state before I made a change. So with like Elm GraphQL,
[00:12:15]
when I'm helping people with these types of problems where they get stuck in too big of a step,
[00:12:19]
one technique I recommend to break down those problems is to just start with getting the type
[00:12:26]
annotation right for this thing you're passing in. Like, if you've got a function that takes like a
[00:12:32]
nested selection set, you know, it's like you've got a person, you're selecting some fields from a
[00:12:37]
person and you want to grab a couple of fields off of that person, then get the type annotations
[00:12:43]
correct. And you can even use, you know, selection set dot succeed, which you can annotate with any
[00:12:51]
type annotation. So once you do that, now you know the problem you need to solve. It's this one small
[00:12:58]
problem. I need to build up something of this type, which is not such an overwhelming problem.
[00:13:04]
You know exactly what the goal is. You know if you're making progress towards it or not. You've
[00:13:07]
put in a type annotation on this smaller piece, and therefore the compiler is going to give you
[00:13:13]
even more fine grained feedback on that small part that you've labeled with the annotation.
[00:13:18]
And so suddenly, now it may be that the problem was somewhere else, or there are other problems
[00:13:23]
you have to fix, which is also good feedback. But you want to find a narrow thing to solve and hone
[00:13:30]
in on that and just follow that path through until it's fixed instead of like working on 10
[00:13:35]
different problems at once. This is another thing I see often, you know, just, you know,
[00:13:39]
like you were saying, having like you're changing the way you model some data, your model changes,
[00:13:46]
you're wiring in a new message, you're changing the view, you're doing all these different things
[00:13:51]
at the same time. It's like when you get used to this way of working in tiny steps,
[00:13:57]
all of that sounds extremely stressful. Yeah, it requires a bit of discipline not to do
[00:14:04]
too many things at once. But if you get in the habit of doing things in tiny steps, like, okay,
[00:14:11]
that discipline is just asking you to hold off for like a minute or 30 seconds. Stash everything,
[00:14:19]
stash everything, rename that model field, commit, then unstash and you're back to where you wanted
[00:14:29]
to start this whole refactor or something. Exactly. A new feature. And you know, one tiny step that
[00:14:37]
maybe doesn't get the credit it deserves is writing something down in your to do list.
[00:14:45]
If something comes up, you're like, I want to refactor this, I want to, you know, like,
[00:14:50]
change this value in the model, I want to wire in this message, just have a to do list of the things
[00:14:56]
that you're doing in the next 10 minutes or whatever, that's fine. Like, don't just start
[00:15:01]
writing the code, just write it in your list, and you'll get to that next. But don't start it until
[00:15:07]
you finish the thing you're doing right now. Yeah, the good thing with the compilers,
[00:15:11]
it tells you what you need to fix. So if you do all the changes, if you start all the changes at once,
[00:15:18]
that the compiler is your to do list. But if you do tiny steps, then you can't use a compiler as a
[00:15:28]
weird kind of to do list. And yeah, to the list will then be a better to do list than the compiler
[00:15:34]
to do list. Right, right. So one technique that I think is worth talking about here, starting with
[00:15:41]
a fake value is a really good starting point. But then how do you take that fake value and turn it
[00:15:48]
into a real value? And there are a lot of techniques that help with that. So I really like
[00:15:54]
this technique of separation, Luell and Falco calls it separation, I think, kind of pulling apart
[00:16:00]
these fake values and splitting them up into multiple fake values, like taking a string and
[00:16:05]
then separating that string into two strings that are concatenated. That's like a tiny step that
[00:16:12]
allows you to take this big hard coded thing and turn it into two smaller hard coded things. And
[00:16:19]
that gives you a sort of insertion point where that you can remove some of that hard coding. So
[00:16:24]
once you have a fake value, then you want to take tiny steps to make it real values. But you don't
[00:16:32]
just put in a fake value and then replace that with a working implementation where everything is
[00:16:38]
there. So if you run through this example, like for instance, you have someone on your page,
[00:16:44]
you have a text that says, Hello, Dillon. Mm hmm. Mm hmm. So you start by hard coding, Hello, Dillon.
[00:16:51]
Yes. What you then do is like HTML that text. Yeah. Mm hmm. Hello, Dillon. So what you then do
[00:16:59]
is you concatenate Hello and Dillon. So one thing that you will need to do then is probably put
[00:17:05]
parents around it. Otherwise, it will not compile. Mm hmm. Yes. Something that you would have run
[00:17:11]
into later as a compiler, but that you could fix right now with just this
[00:17:18]
very tiny step. Mm hmm. Yes. And then what you and I would even do a little step in between there,
[00:17:25]
which is I would commit that. Yeah, yeah. I know it seems I know it seems really extreme, but I
[00:17:31]
really do make very small commits. Well, you know, the the best benefit of tiny commits is that very
[00:17:40]
nice commit contribution graph on GitHub. I know. I know. It's true. Yeah. My repos all.
[00:17:47]
You made 150 commits today. Wow. He's so active. Exactly. My repos really have like massive
[00:17:55]
commit counts on them. It's funny. Yeah. Yeah. So you separate those two in Helen. Hello, plus Dillon.
[00:18:03]
You commit that. I'm not going to say commit every time, but yeah, then you can just run
[00:18:07]
it. I'm not going to say commit every time, but yeah, then you extract Dillon into a variable.
[00:18:13]
Yes, exactly. Lead variable or something. And then maybe you want to pull it out from the model.
[00:18:19]
So what you then do is you remove it, remove that lead variable and use model dot name.
[00:18:29]
And then we'll tell the compiler, tell you, oh, well, name is not a field in model. OK,
[00:18:34]
add that field and how you could hardcoded to Dillon. And then you need to get it through a
[00:18:43]
dynamic value somewhere. So maybe you need to decode it. So you will add that decoder.
[00:18:50]
Yes. And you can hardcode that with the decoder succeed because that's yep.
[00:18:55]
Succeed is the key to success. Exactly.
[00:18:58]
And then once you have that, you can probably just do decode dot string and then get it from a real
[00:19:05]
value somewhere. Yeah, that's right. At that point, you've pretty much wired it all the way
[00:19:10]
through. Yeah. So and all in all, we did what we did. One, we avoided one quoted tricky compiler
[00:19:19]
issue with text having two arguments or something. We added a field to the model. We decoded it.
[00:19:25]
And every step of the way was very easy. Whereas if we did that to start with, then
[00:19:33]
yeah, it would have been too much. This is the way I did it previously. Like, oh, I need a name.
[00:19:40]
OK, so let me add it to the model. Oh, now I need to get it from a value from the HTTP request. OK,
[00:19:49]
so I need to decode it. And then I do the whole update to do everything. And then I
[00:19:54]
display it. And yeah, when I'm there, it's done. But I didn't have a small feedback loop. So
[00:20:04]
I couldn't notice that something was wrong until I was in the middle of it. And as we said,
[00:20:11]
if you're already doing something like adding a new feature, you don't want to change everything
[00:20:17]
because your assumptions were proven wrong at some point. Exactly.
[00:20:22]
Exactly. It's all about validating the assumptions as fast as you can. And with a very straightforward
[00:20:28]
problem like taking something from a flag, decoding it, showing it in the view, that might sound
[00:20:37]
forced. And it might be. I mean, we would probably be fine. We would probably be able to do it just
[00:20:46]
as quickly and with just as little risk if we did it, you know, decoding it right away. But that's
[00:20:54]
not that's not why it's interesting. It's interesting when you're solving the really
[00:20:59]
difficult problems and you have that skill set. Now it's really powerful. So, you know, I was just
[00:21:05]
working this last week on a feature in Elm pages where I'm I'm getting rid of Webpack and doing
[00:21:14]
a new build process, which I'm very excited about. It's kind of intimidating, you know, it's like
[00:21:19]
it's a big change. And I mean, I can't even imagine just working in the dark for days and days and
[00:21:27]
days, like building all these different features like, oh, I'm going to need something that outputs
[00:21:33]
this HTML and uses this template for the HTML and then, you know, fetches this data and whatever.
[00:21:39]
It sounds like a nightmare to just be working in the dark. So I want something that works right
[00:21:45]
away and to just even know that I'm going to be able to wire these pieces through and the
[00:21:50]
architecture is going to fit like I need to know that right away. Otherwise, it's just terrifying.
[00:21:56]
So and that's that's what I did. I started with just getting a hard coded thing working end to end
[00:22:04]
as fast as I could. And I did that by just saying, OK, I'm I'm going to assume that there's one page.
[00:22:11]
I'm going to assume that I'm I'm, you know, doing a Webpack free build for a site with exactly one
[00:22:18]
page. And if it doesn't have that, then I'm going to fail. And I'm going to assume that there's not
[00:22:26]
an error. And I'm going to like make all these assumptions and just cut through this one path.
[00:22:31]
So instead of doing every case partway through, you want to do one case all the way through end
[00:22:38]
to end and see it work. Because if you don't if you don't do it end to end, you can't see it
[00:22:42]
working. So and if you start trying to handle every case, it's only slowing down your ability
[00:22:50]
to see it working end to end. And what you want to do is you want to see it working end to end as
[00:22:55]
soon as possible, which means just relentlessly focusing on that one case getting all the way
[00:23:00]
through. So so that's what I did. I got it all the way through, made as many assumptions as I could.
[00:23:06]
And and then I started iterating from there. And I had a test harness that I could iterate on and
[00:23:13]
and do those other cases. So with more difficult problems, it's so valuable to to have that skill
[00:23:22]
set of taking these tiny steps. Yeah. So I told you before this that this weekend, I was working
[00:23:29]
on some kind of type inference for Elm review. So yes, there are some rules that check whether
[00:23:35]
you have type annotations for your top level declarations and your lead variables. And just
[00:23:43]
told you, hey, you need to type annotation here. Okay, thanks. That's not very helpful.
[00:23:48]
So I started working on having those type annotations work be auto fixed so that you
[00:23:56]
don't have to do anything manually. And what I did was I started really small, like because I need to
[00:24:02]
infer the type of thing. So what I did was I will do the whole thing for if the variable is clearly
[00:24:11]
a string. Oh, cool. Yeah. So if I see a literal string as a no, okay, just add the type annotation
[00:24:19]
where it says that it is a string. Because for the built in type, you can't do that for numbers
[00:24:24]
necessarily, because you don't know if it's an inter float. But for a string literal, you can just
[00:24:29]
give that fix. Yeah. Well, you for number you could but you just say number. Yeah, although it's less
[00:24:37]
satisfying. It is less satisfying. But that's what you got to do. So yeah, I made the whole thing
[00:24:44]
work for strings. So cool. Once I had that I could already run it on some of our projects and it added
[00:24:51]
strings everywhere. That's amazing. And that's and you've both done no work and a lot of work. Yeah,
[00:24:58]
exactly. That's a great example of that. And then yeah, I handle numbers, I handle the type
[00:25:03]
annotation, I handle hex literals, I handle parens, I handle binary operations, I handle lookups to
[00:25:11]
other variables. And all these things, they how do you say that they use each other? Yes, they build
[00:25:19]
off of each other. Yeah, exactly. Right. So every step is quite simple. Yes. And then very nice thing
[00:25:27]
is that since it's easy to test, I write tests for each step. And now I have a huge test with just
[00:25:35]
for type inference. Right. Yeah. And you've got the wiring, you get to build all of the wiring,
[00:25:42]
which is not easy. Like building wiring is hard. But you do that for string, like for if it's a
[00:25:49]
literal string, then the type is string. Yeah. So the first step is to build the type. And then
[00:25:55]
you build the string. Yeah. So the first part might have been the hardest. It's not, but it might
[00:26:01]
have been. And what is also nice is that I can already use this. I can already add type
[00:26:11]
annotations for plenty of things and just don't for all the things that are too complex yet or
[00:26:16]
not handled yet. So I can already see whether there are errors and I can go fix those. If I did
[00:26:23]
it the other way where I try to build the whole type inference things first. Well,
[00:26:31]
I would not be as happy today. I would still not have anything. And I actually wouldn't know
[00:26:39]
if I'm any closer to the destination. Exactly. And I guess that finding bugs. You'd be lost in the
[00:26:45]
woods without a compass. Yeah, exactly. And whenever people tell me, oh, you have a bug,
[00:26:50]
then yeah, I guess somewhere I need to find it, but now I can detect bugs as I go. Yes.
[00:26:57]
By trying it out. That's an amazing example. I love that. There's also one thing that you get,
[00:27:05]
even with very tiny steps where you hard create a thing. Like, you know, when people say,
[00:27:11]
when it compiles, it works, you feel happy when that happens. There's a tiny bit of dope and
[00:27:17]
new rush or whatever. But if you multiply the number of times that thing succeeds,
[00:27:24]
you have like, I won, I won, I won, I won, I won, I won. Yes. So many times that it's just like,
[00:27:31]
I always feel happy. Yes, exactly. It's true. I mean, there is a stress level when you've got
[00:27:39]
something up in the air and you don't know if it's going to be working like that feeling of like,
[00:27:43]
oh, come on, please compile, please compile. That goes away. And it becomes like, okay,
[00:27:51]
it is compiling. And then you do a small step and you're like, you do a tiny step and you're like,
[00:27:55]
oh, I hope this compiles. Oh, it didn't. Okay. I'll just go back to my starting point or revert
[00:28:00]
or whatever, you know, try again. Also, every time you save your file,
[00:28:05]
you get Elm format to format everything. So your code is more readable at every step.
[00:28:11]
So that's also one nice thing I found. Yeah. I think that working in this style of taking
[00:28:17]
tiny steps, it becomes harder to take small steps if you're not confident in the quality of your
[00:28:25]
code. So, you know, for example, if you, if you are working in code that has a lot of bugs,
[00:28:33]
it doesn't have tests, it has, you know, very large functions that don't have good data types.
[00:28:40]
And it's just difficult to work with. Then it's going to be hard to take tiny steps. So, and what
[00:28:46]
you're probably going to end up with in a situation like that is you're probably going to have a
[00:28:52]
manual testing process where, you know, eventually you end up with, you know, a testing team,
[00:28:58]
manual testing team, and they take a couple of days or a week to test the code. You have a code
[00:29:05]
freeze. And so now it's actually, it's the opposite of tiny changes. You make any change and you are
[00:29:14]
afraid of shipping it. And so you, you take a long time, your feedback cycle gets even longer because
[00:29:21]
not only can you not make tiny changes, but those tiny changes don't even get into the, into the
[00:29:27]
finished product until a few weeks later. So there's like a skill set around tiny changes and a set
[00:29:36]
of habits. And you can really learn those by, I mean, I think that practicing test driven development
[00:29:44]
doing Kata's is one of the best ways to learn those skills. And you really can learn them. But then
[00:29:51]
from the other, like the skills are like sort of on one side and on the other side are like sort of
[00:29:56]
the organizational pressures and processes. And you also need to approach it from that end and
[00:30:03]
make sure that you don't have things that are lengthening your feedback cycles. And it's all
[00:30:09]
about building in quality at every step because a tiny step doesn't mean, it doesn't mean getting
[00:30:17]
it compiling in one small step. That's one part of quality, but if it's compiling, but there's a bug,
[00:30:25]
the step is not done. It's not a step.
[00:30:27]
Unless it's a bug that you know and for which you have written a to do or...
[00:30:32]
Yes.
[00:30:33]
Like, like I'm not going to handle the error case. I'm not going to.
[00:30:37]
Exactly. That's right. So the process is you don't need to handle every case. So you don't need
[00:30:45]
completeness, but you need correctness for every case that you handle, it needs to be correct.
[00:30:50]
And that's, it's complete once it's correct. And what happens is the, I think maybe the more
[00:30:57]
intuitive way to work is just to work on several things at once and try to, try to get to completeness.
[00:31:05]
And so you're moving in all these areas, getting, getting things towards completeness
[00:31:11]
and working towards correctness at every step. And the shift is you get things correct very
[00:31:17]
frequently. It's just very small things.
[00:31:20]
Yeah. As you said before, like take one path, the path that you really want to work and make it
[00:31:27]
work from end to end. So don't go for completeness yet. Just go for this path and hard code or mock
[00:31:36]
or debug to do the other paths.
[00:31:39]
Right. So now that's, that's another interesting case, the debug.to do. So I do make use of
[00:31:46]
debug.to do and it's a very helpful tool, but let's talk about that a little bit. When is debug.to do
[00:31:52]
the right tool and when is it problematic? When does debug.to do get in your way? Do you have any
[00:32:00]
thoughts on that? I have some.
[00:32:01]
It's not a tool that I use as often as I think I should. I do find it more and more useful.
[00:32:09]
The thing with debug.to do is that it allows you to write whatever.
[00:32:13]
Yeah. Maybe we should define debug.to do.
[00:32:17]
Yeah. So debug.to do just crashes your application. In Elm 018 was called debug.crash.
[00:32:25]
You give it an error, a string, and when debug.to do will be encountered, it will just crash.
[00:32:34]
Right. And the type is any. It's basically Elm's version of the any type, except instead of just
[00:32:41]
saying that some actual value is any type, it just won't execute it.
[00:32:47]
Yeah. And that is kind of dangerous, but you can't use the optimized flag with Elm make.
[00:32:55]
So you will not see this in production, so it's safe.
[00:32:58]
Mm hmm.
[00:32:59]
So the nice thing about it is that it bypasses the compiler. So you can use it instead of any
[00:33:07]
function or any type, whatever. The downside of that is that you can use it instead of any type.
[00:33:15]
So when what you want to get is a good type, like you want to make sure that a type is correct and
[00:33:24]
will be useful and that you have all the information that you will need, then it might not be the
[00:33:29]
right idea. Because you're not validating one path. Like, oh, I can show an error if I enter this
[00:33:40]
error case, but maybe you can't because you don't have all the information needed yet.
[00:33:44]
And debug.to do will not give you that information.
[00:33:47]
Right. Yeah. One thing that debug.to do is very helpful for is, in that case, I was talking about
[00:33:56]
earlier where you're trying to figure out the right type for something in Elm GraphQL. Some
[00:34:02]
function takes some selection set of a particular type. Then you know I need to pass something in
[00:34:10]
here, but I don't know how to build that thing. And in that case, you can just pass in debug.to do.
[00:34:18]
And that actually answers an interesting question, which is, is there any type that would fulfill
[00:34:23]
the type system here? If it's not compiling when you pass in debug.to do error message as the
[00:34:30]
argument there, then there's nothing that will satisfy the type checker.
[00:34:34]
I've never had that experience yet.
[00:34:38]
Yeah. I really like doing that. Maybe I'll have a chain, a pipeline of functions that I'm applying
[00:34:49]
and I know I start out with this value. I know I need to eventually get it into this function of
[00:34:57]
a particular value, but then is there some way to transform it into this type of value? So maybe I
[00:35:04]
say list.map parentheses debug.to do error string. That answers the question for me. Is there any
[00:35:12]
function that I can pass into list.map in this pipeline here that will satisfy the type checker?
[00:35:18]
So now if it's compiling, the compiler has just answered that question. Yes, there is. There is
[00:35:24]
such a function. You could have a function that satisfies the type checker when you call list.map
[00:35:29]
on it. If it's not compiling, then hmm, what am I doing wrong here? Oh, it's not a list. It's
[00:35:36]
actually a result string list of something. So now I need to do result.map around that list.map
[00:35:46]
or something. And now, so now I do result.map parens list.map parens debug.to do string.
[00:35:54]
So you're basically using debug.to do to figure out, are there any type problems in other places?
[00:36:00]
You ignore the ones that are local to where you put the debug.to do, but you highlight the ones
[00:36:06]
that are somewhere else. Yeah. So it's just telling you like, does the connection fit? Do these jigsaw
[00:36:14]
puzzle pieces fit together here? And so at that point, what I would do is I would take that
[00:36:22]
function that I was using debug.to do for, I would expect it to be a function that would
[00:36:28]
using debug.to do for, I would extract that debug.to do into a little let binding. And then
[00:36:35]
I would try to get a type annotation for it. And now once I've got that type annotation, so it's a,
[00:36:42]
you know, a function from string to int. Now I know that the compiler is happy and now I'm going
[00:36:50]
to just return a hard coded int of course, right? Because now I've got a real value. So I've faked
[00:36:57]
it and I've got it working all the way through. So debug.to do can be really handy for those cases.
[00:37:04]
I think that it's important to, you know, you want to use debug.to do as a tool to sort of like,
[00:37:11]
you know, you're sort of bulldozing through a problem. And I don't, you know, you don't want
[00:37:16]
to keep the debug.to dos in there too long necessarily. I usually use them as a shorter term
[00:37:21]
tool. There are, you know, there are times when I'll sort of like ignore an error case or something
[00:37:27]
like that. But if you use debug.to do, for example, to, instead of having a result, because you
[00:37:34]
ignore the error case and you just say, if it's an error debug.to do, you're actually sort of
[00:37:40]
deferring dealing with this problem that you're going to have to deal with. And you don't have
[00:37:45]
working code that whole time. So you can't ship it. So you've just structured your code in a way
[00:37:51]
where, you know, I was using this distinction of complete versus correct code that your code
[00:37:58]
doesn't have to be complete, but it does have to be correct. Well, it's not exactly correct. It's
[00:38:04]
not shippable when it's in that state. So that's not quite in the spirit of tiny shippable steps,
[00:38:11]
you know, that's sort of the goal is that they're shippable quality steps and you can't ship it. So
[00:38:16]
you can get yourself stuck in this situation where you're working for a long time with code that's
[00:38:21]
not shippable. So that's something to be aware of. I think that debug.to do is a very useful tool
[00:38:26]
for figuring out what step to take, but then follow through that step. And when there's a
[00:38:30]
debug.to do, maybe this is a good rule of thumb. So you can use debug.to do for sort of exploring
[00:38:39]
if types line up, if you're like designing an API, that's more in explore mode. You're not like,
[00:38:44]
your goal is not to build code. Your goal is to like learn and you could even throw that away
[00:38:49]
when you're done, you know, or you can use debug.to do to identify a step and then you
[00:38:56]
follow that step through. So just be sure that you're using debug.to do, you know, you figure
[00:39:02]
out the path and then you go down that path. You don't like ignore it for a long time. That's to
[00:39:08]
me, that's not really, that doesn't feel right when I use debug.to do that way.
[00:39:12]
Yeah. I guess when I've done the end to end scenario work, I guess I would go for the debug.to
[00:39:19]
do afterwards because those are like places where you still don't know whether things will work out
[00:39:27]
or not. Those are known unknowns and it's best to go and figure those out.
[00:39:34]
Exactly. That's exactly it. That's exactly the mindset is like you want to,
[00:39:41]
you want to, anything that's a risk, you want to get out of the way as soon as possible.
[00:39:46]
And if it's a debug.to do, you're cutting yourself off from understanding whether it's going to work out.
[00:39:53]
You're deferring that feedback, you're lengthening the feedback loop. So it's really all about feedback loops.
[00:39:58]
This is a question I asked you after the evening where we met. How often do you need to write to dos?
[00:40:07]
Because you're writing a lot of dummy code, a lot of hard coded code when you do this.
[00:40:12]
How do you make sure that you don't forget it?
[00:40:16]
How do you make sure that you don't forget to handle it?
[00:40:19]
I write to dos all the time. When I'm coding, I've always got, I use bare notes. It's like a
[00:40:27]
markdown notes thing. And I just put in like to do items as they come up. And I work in
[00:40:34]
Pomodoros most of the time, which is like you've set a 25 minute timer and work with no distractions.
[00:40:40]
And then you take a five minute break after. And when I'm getting close to the end of my Pomodoro,
[00:40:45]
I'll often make sure that I take all of the context that I had and dump it out into notes.
[00:40:52]
So I'll write down all the to dos that I can think of, but I'm constantly writing to dos.
[00:40:57]
And you do that at the end. You don't do it every time you hard code something?
[00:41:04]
I do both. If something comes up and I'm like, oh, I need to do this, then I'll write it down.
[00:41:09]
But then sometimes I need to take a moment to sort of collect my thoughts and think about where I am
[00:41:14]
and say like, okay, what are the next steps here? And I usually try to set myself up for being able
[00:41:21]
to jump in and have context quickly when I start my next Pomodoro. And so I'll try to capture all
[00:41:28]
of those to dos and thoughts in notes before I lose that context and then set myself up to get
[00:41:34]
a good start on the next one. Gotcha. And do you also write what the next end to end
[00:41:40]
scenario you want to fulfill is? Like I want to have an add button. I want to have a delete button.
[00:41:47]
So you do the add button, you mark some things and then you do the delete button.
[00:41:52]
Yes, I do. I do that. I usually try to write the to dos in terms of like
[00:42:05]
features. Sometimes I'll have like a heading that will be like, have a delete button. And then,
[00:42:11]
what do I need to do to do that? And then I'll try to break it down into steps that I could do
[00:42:15]
incrementally in tiny steps. But I do try to choose words that reflect the end to end thing
[00:42:23]
rather than the implementation also. And then maybe I'll write a few notes on the implementation
[00:42:28]
if I want to capture a thought. As a developer, I want this button to use stories. Yeah.
[00:42:37]
Yeah, because you want to keep yourself honest about doing something shippable.
[00:42:42]
Yeah. And the words that you use can help guide you towards a shippable step and getting a smaller
[00:42:49]
path to getting something shippable. Yeah. Implement function name is not very useful.
[00:42:55]
Exactly. Yeah. Yes. And on the topic of debug.todo, I just wanted to share one last thought
[00:43:03]
there for anybody who's maybe new to debug.todo. It's okay to use debug.todo in your tests.
[00:43:10]
And it's okay to completely ignore certain cases and tests. That's fine. You don't need to compile
[00:43:17]
your test code with the optimize flag. You don't need to get rid of debug.todo in your test code.
[00:43:23]
If you have something in the test code where you expect to, you know, I don't know, you're
[00:43:31]
decoding some value that you're getting for your test setup and you expect the decoder to succeed.
[00:43:39]
You can just do debug.todo, you know. Yeah, definitely. To handle it. That's fine.
[00:43:45]
Yeah, because if you try to do it the Elm way, then you will probably end up with a test that
[00:43:54]
does nothing otherwise. Right. Or the test fails and says, you know, you have to wire in that test
[00:44:02]
failure, which would be fine, but you don't need to. You can just crash the test. It's okay. Yeah.
[00:44:06]
Debug.todo is a test failure in itself. Exactly. Elm review has a rule that says no debug.todo.
[00:44:15]
And in the documentation, I say it's okay to do it for tests. So just ignore this rule for tests.
[00:44:21]
Cool. Yeah. Yeah. Nice. Because it's very useful there. So I think there's one thing that we
[00:44:29]
haven't hit on. I mean, there's so much we could talk about here. I'm sure we could talk about
[00:44:34]
more, but there's one big thing that we haven't really gotten into too much. And we can touch on
[00:44:41]
it briefly here. I think it's probably a topic for another episode, and that is refactoring. So
[00:44:47]
I think that refactoring plays a large role in this. So sometimes you can't take a small step
[00:44:55]
because of the way the code is structured. So you need to refactor. Yeah. Therefore,
[00:44:59]
small steps and refactoring are connected. I often refer to this Kent Beck quote,
[00:45:07]
which I really like, which is, make the change easy, then make the easy change. And he says,
[00:45:12]
warning, the first part may be hard. I totally thought that this was from you.
[00:45:18]
I was so disappointed to learn that it wasn't. I used to try to credit Kent Beck pretty explicitly.
[00:45:24]
I thought it was yours. I was like, wow, that man has some words of wisdom.
[00:45:31]
And I know it's great. Oh yeah. I will ruthlessly steal any nuggets of wisdom.
[00:45:39]
I do try to credit them, but yeah, there's, I mean, all of these concepts, fake it till you
[00:45:45]
make it, short feedback loops. And I mean, we're standing on the shoulders of giants here. Kent
[00:45:51]
Beck really pioneered a lot of these ideas and it's pretty, I find his thinking on these things
[00:45:58]
very elegant. But yeah, refactoring is intimately connected to this concept of small steps.
[00:46:06]
And refactoring can itself be done in small steps. In fact, I think that often what people
[00:46:13]
use the word refactoring for is not actually refactoring. It's a rewrite, which can be fine
[00:46:20]
in some cases, but I think it's a difference. A refactoring is an atomic transformation
[00:46:30]
of the structure of the code without changing the behavior of the code. A rewrite is just
[00:46:38]
getting rid of some code and saying, okay, I'm going to re implement this.
[00:46:41]
They're two different things. So if you do a rename refactoring, then it's just like,
[00:46:47]
and you can think of it as one atomic thing, just boom. Now the name is this. If you extract
[00:46:53]
some function to a module, ideally it would be great to have, you know, IDE support to do that
[00:47:00]
in an automated way, but you can do that manually and you can, you can do that as a safe atomic
[00:47:05]
step. But also, you know, as you mentioned earlier, if you're like extracting a new module
[00:47:11]
while implementing another thing, just go ahead and extract the module first. It's, it's a habit
[00:47:17]
and I, I really encourage people to try that habit out to separate your behavior changing steps from
[00:47:24]
your structure changing steps and try to like, if you can refactoring is so nice in Elm and if you
[00:47:32]
can make a change with no concern that you're changing behavior at all, that's a huge win.
[00:47:38]
It's less to think about. You, you think about the refactoring and it's dead simple and then you make
[00:47:45]
the change and it's more simple than it would have been if you're trying to do both at the same time.
[00:47:49]
So to me, it's like just a very logical thing, thing to do. It's just hard to build that habit.
[00:47:57]
So I think maybe we'll leave refactoring as a teaser for a future episode and we can dive into
[00:48:03]
that more another time. I do feel like when you do tiny steps, you will feel the pain of bad
[00:48:10]
structures more easily. So you will notice when things need refactoring way more quickly.
[00:48:17]
Right. And as you, as you pointed out earlier too, it's sometimes you start trying to do a tiny step
[00:48:25]
and then you kind of get stuck because you're like, Oh, I, now I need this thing. I need this
[00:48:32]
code to be this way. I need it to take this argument, which I don't have, I, you know,
[00:48:36]
whatever. Well then that's fine. You get stash your changes, you wire in a new argument. You've now
[00:48:45]
made the change easy and now you can get stash pop and make the easy change. Now that you've got
[00:48:51]
the argument that you needed. Well, how should people get started trying out this technique?
[00:48:57]
Well, my main learning from this is to try and shorten your feedback loop.
[00:49:02]
And the biggest thing I've took out of it is try to make your code compile be the first thing,
[00:49:11]
be your priority. So when people say it's nice when you do a huge refactor or rewrites,
[00:49:20]
it lasts one hour and at the end it works when it compiles. That's great. But if you can make
[00:49:28]
the fact that it compiles your priority, you will not feel the pain of that one hour of rewriting.
[00:49:34]
Exactly. So yeah, don't do multiple things at once. Focus on one thing and focus on getting
[00:49:42]
compilation working and test passing. Yeah. Completely agree. I think that's,
[00:49:48]
if you learn one thing, that's probably one of those. Commit often. And
[00:49:55]
as you said before, you could learn a lot from Katas. So go watch Deniz's talk on incremental
[00:50:03]
steps or what was it again? Tiny? Type driven development.
[00:50:07]
Type driven development. Yeah. Or I thought he was going to talk about test driven development
[00:50:13]
because he said it was about TDD. And I basically did except using those principles
[00:50:19]
without the test part. Yeah. And there's also a very good talk about applying automated refactorings
[00:50:30]
from Llewellyn Falco. Yeah. He's got a lot of good talks.
[00:50:38]
Yeah. I really recommend watching those. Are you talking about practical refactoring?
[00:50:43]
Is that the talk with the two minute timer? Yeah.
[00:50:47]
I was thinking of the one from the Gilded Rose Kata.
[00:50:52]
Okay. That's an amazing one too. Actually, I'll put a link to this talk that Llewellyn
[00:51:00]
Falco and Woody Zewald gave about, it's called practical refactoring. That is an amazing talk.
[00:51:06]
It's so good. They take a two minute timer and they have two minutes to get the code
[00:51:12]
to a shippable state for every change they make. Oh, nice. I highly recommend that talk.
[00:51:19]
And I wanted to point one thing out. I could not agree more with everything you said about the core
[00:51:23]
idea and following one thing through, not having multiple things that you're working on at the same
[00:51:29]
time and noticing when you're doing an hour long refactoring. I just wanted to point out
[00:51:35]
for anyone who's new to this sort of approach, there are times when you're doing refactoring
[00:51:42]
that sort of take some time because you're wiring in a new argument and that's one small atomic
[00:51:47]
refactoring, but you have to change 20 places. Yeah. And that's okay. So.
[00:51:53]
Because you can't get it any smaller. Yeah. There's no way unless it's automated. And again,
[00:52:00]
these are the types of things that can be automated. Elm has all the ability to be
[00:52:06]
statically analyzed and automated in those ways. And I think that's a great point.
[00:52:10]
So you can't get it any smaller than that. And again, these are the types of things that can be
[00:52:15]
statically analyzed and automated in those ways. And those things will come. But for now, it's okay.
[00:52:21]
Just pay attention to it could be taking a while, but are you just doing one small refactoring or
[00:52:27]
are you doing a rewrite of all of the code? And how, and if so, how can you break out one small
[00:52:33]
piece, do that refactoring? It's kind of like the doing starting at the leaves of the tree
[00:52:40]
whack, but you want to start up at the leaves and then move towards the root. So yeah. And well,
[00:52:46]
I wrote an article recently called relentless tiny habits. So you might want to give that a read.
[00:52:52]
It kind of talks about some of the principles we've been talking about and sort of how you
[00:52:57]
build those habits into your way of working and how much that relates to taking tiny steps
[00:53:04]
is not something you just do. It's a habit that you continuously build. Yeah. Anything else to
[00:53:10]
point people to? I think that's quite a lot of resources already. I think so. I think so.
[00:53:17]
This takes, this will take a lot of practice. Like even I am doing more and more tiny steps,
[00:53:22]
but I still don't go end to end. And that is something that I need to practice for instance.
[00:53:29]
So yeah, a lot of habits. Yeah. And they are habits and they are as with any habit.
[00:53:34]
If you do that step one time, then you've started a habit and you just need to keep doing that.
[00:53:42]
Also, as with any other habit, you know, you can, you can get better or worse over time. So
[00:53:48]
it, it's a continual learning process. It's something that requires constant, constant care
[00:53:54]
to take care of that habit. But code cadas are a great way to keep that up. I think setting aside
[00:54:01]
some deliberate time where you say, I'm going to work in this way without the pressure of
[00:54:08]
production code. I think that can be very helpful. You know, you can apply these ideas to side
[00:54:13]
projects too. I think you and I would, would both share that experience of learning new techniques
[00:54:19]
by sort of trying to be our best selves in our, in working on a side project. Yeah, definitely.
[00:54:25]
So that's another way, but I, I would recommend employers or, you know, developers to set aside
[00:54:32]
some time and give, give developers some time to do, you know, to practice, to try things out, to,
[00:54:38]
to do exercises and cadas. I think that's a great way to learn these habits. Yeah. With a focus on
[00:54:44]
what you want to learn. Yes, exactly. Short feedback loop. That's not just how you write good
[00:54:50]
code. That's how you learn too. Yep. All right. Well, this one was a lot of fun and don't forget
[00:54:59]
to review us on Apple podcasts, follow us on Twitter and thanks for listening. Yeah. And if
[00:55:06]
you have a question, go to elmradio.com. Yes. elm dash radio.com slash question, or just elm
[00:55:13]
dash radio.com and click the question button. Yep. And Dillon, see you next time. See you next time.
[00:55:19]
Take care.