I'm blogging along with the laracasts series Pest Driven Laravel created by Christoph Rumpel as I work on a project I hope to open source. I'm just documenting how I'm working, the resources I'm using, the problems I run into and some cool things I'm reminded of as I develop.
This is another relatively nontechnical post. It's a longer one about learning and TDD. This series has been conversational. It's a little bit of an exercise in failure, part performance review. Here's a whole bunch of opinions:
I think there comes a points in every developer's career when they understand the fundamentals, understand a bunch of "known unknowns" about what they need to learn (in the sense of information consumption/acquisition), know a few "known unknowns" about what they need to improve (in the sense of mindset/style) and then see what they're able to produce. This is the stage of any new discipline when I start to engage with how I'm learning. I do this in lots of ways: consuming information about known knowns (taking courses, following tutorials, reading documentation, reading books, watching videos) and known unknowns (watching streamers with a lot of experience and/or a systems-thinking approach, listening to podcasts where people discuss coding in more abstract terms, reading books like 'the pragmatic programmer' and 'clean code', reading code that is well written).
So I get this input and I have to deal with how that doesn't line up with my "known knowns". I see it in my output, and I want to get better, but it's not possible to prepare for unknown unknowns or to avoid them. Whenever one is learning a new skill, the unknown unknowns are the most important pieces.
It's good to take a break and think about how to get from point A to point B in the following series of things. Here's some definitions:
A. output: producing code that is fine, I guess, but could be implemented better (known knowns). Your body of work until now.
A′ (A "prime"): producing output that is pretty ok. Your current project(s). You don't have to google much to work in this area
B. improved output: the a body of work you're currently challenged when youre interacting with, where you start ennumerating and sometimes incorporating things you know you should know (known unknowns)
C. improved thinking and processes: finding a better way to incorporate things you know you should know (known unknowns)
X. idealized output: producing code that contains things you know you should know with a better process than you have now (unknown unknowns)
It is my personal opinion that no one ever gets to point X (idealized output). Instead, we aim to shift the goalpost. And as this happens, point A (output) improves until the output looks like point X (idealized output) to someone else. Let's call that space A′ (current output that seems a little better than previous output). And all the things that happen at point B ("reach" output) and the behavior that happens at point C (improved thinking) are too much to impart directly to another human because
- it would take too long
- expressing everything that happened during a shift like this delays or even prevents another
The production of educational material for other people is a side effect of C (improved thinking/processes). But writing code productively at point A′(current code after learning something) and writing the best tutorial or course material after learning something are not the same activity at all. The best tutorial you can write at a given point A is more like an emergent property than a part of this system at all.
The ability to talk about code as you're doing it can also be a side effect of point A′ (live coding in your comfort level or describing code you've already completed) but also a completely separate process. It's harder to verbalize code at point B (improved output) and REALLY hard at point C (improved thinking) before a new concept has been integrated into long term memory and can be applied to additional processes. Interview prep and pairing can increase our capacity to describe what we're doing and thinking as we code as it is being written.
Because it's difficult to ask a person about what they're learning and easy to ask a person about what they're comfortable with, I think it's also a limitation of live coding interviews. I also think evaluating more recently integrated concepts and how comfortable a person is at point C (improved thinking) is a more valuable evaluation of a programmer than their A′ (body of work). This makes the methodology of producing and grading performance evaluations vastly more complicated. It's also a valuable quality to be comfortable going through this A->B->C->A′; progression in rapid succession over and over and over again. There's a marginal increase in cognitive load and discomfort in these iterations. ESPECIALLY for new programmers.
Occasionally when you're trying to improve, you encounter a complete dumpster fire of a situation and have to think about how to recover from it, like a Giselle in the wild shaking itself off after it realizes that it's been debugging in the wrong directory for like 8 hours. Mixed metaphor there. There can be an emotional aversion that comes with this type of block.
I'm taking this course where I'm watching code being written in the frameworks I'm using in my project. But when I finish the course, I'm not going to be at the instructor's point A when he wrote the course. I'm going to be at my point X from when I started asking questions about TDD.
Now that I've learned a bit more, I realize it is useful to reorganize my first post in this series with some takeaways I've gotten up the point where I am now. I reserve the right to take all of this back, btws.
1. Pause after every new test passes and ask yourself what you're going to do next.
This is my favorite takeaway from this course. We don't just see coding, we see pausing. This is probably even more important than code.
- It stops me from making reckless decisions and breaking the code I just ensured is working. This is apparently one of my hobbies. It's been pointed out to me by other people "a couple two three" times.
- I can only choose one next thing to do. If something occurs to me after I've made a decision and have begun writing code, I often just forget about it. Once I've reached a milestone with code behavior, an opportunity arises. Valid options of next steps present themselves. Multiple decisions can be made in this moment, but if you rush you may think of a bunch of possibilities, choose one, and go off without fully separating it from the other possibilities. It's hard to stay task-focused with numerous possibilities in mind. I sometimes experience leakage from discarded or postponed options as I pursue the task I've selected. This is not great for code quality.
- Thought is habitual. The patterns we're used to using become learned and the coding patterns we use tend to affect the way we live and vice versa. It's called neuroplasticity (if you are unfamiliar and want to google it) and it's incredible. Coding is a subset active thinking, programmers have a disproportionate responsibility to take regular breaks and manage distractions and interruptions; we also tend to have sprint assignments and meeting-filled schedules and slack/discord channels that sabotage this. If we are impulsive or self-critical or doubt-filled in our coding, we risk the amplification of these tendencies during other activities. There's this concept of "the sacred pause" in mindfulness. Mindfulness is the real benefit of TDD that causes the well-documented side effect of clean code despte the potential drawbacks. I've already seen an improvement in my overall well-being since I started making blade templates just from the randomly generated quotes produced by the inspiring class.
- It gives you the opportunity to write the other possible avenues down and contributes to your engineering daybook. There's this idea that we value a group of potential choices in comparison to each other. Since one can best follow one train of thought at a time, programmers usually have exactly one choice they can pick at each of these opportunity points. We choose the best right thing at the cost of not implementing the other choices. In economics this is called opportunity cost. A forgotten possibility that was the 2nd best or 3rd best option at a moment of opportunity might be more valuable than the 1st best option that comes to mind at the next juncture. It's easy, even probable, to forget about a current task's substitutes while writing code for it.
If I'm in a flow, I'm much more likely to make a choice in my comfort zone and postpone a task I know I would have to research. But I definitely won't research it later if I forget I was going to do it. This is why I've been meaning to look into redis for years. YEARS. Taking time to write down alternative options to implement but aren't choosing to implement now retains value.
Here's the flowchart:
2. Test the smallest thing possible so you have a test.
Write a more complicated test later. Or refactor the test later. When you're in the moment, you want a test that works. Avoid hard coded values in completed tests, but use them when you're making sure your test doesn't break the very time you write it. A failing test is the starting point of the last decision you made, not the final product. Even if the imaginary people who are going to make fun of your code and call you stupid (ignore this if you're not relating, but if you are read this or watch it) are looking at your public github repo, they're not going to your test suites first. That would be insane. So you can be clunky at first.
3. Take time to refactor.
There's a whole video about this in the course. Watch it. Tell me what you think in the comments.
4. Don't make your tests clever.
(See point no 2.) The functional utility of a test is pretty much self-contained unless you're working with arbitrary coverage requirements imposed on you by someone else. If you care about your coverage the simpler the tests you write are the more confident you can be that your code is working as expected. If you have to choose between brittle/fancy and robust/simple, KISS. If you are being forced to test for information that is not useful, whoever imposed those requirements doesn't deserve your clever. Save it for leetcode.
I'm pausing after the introduction to jetstream to re-evaluate my previous project planning. I made a bunch of wireframes (as suggested). That really helped me think about what behavior I wanted to have and how that should be implemented (as promised). Previously, I had organized my goals by role. Now I want to think about testing the smallest thing possible, and planning coverage to match the parts of ui that matter the most. So I'm going to sort them into actions instead, because this is how I will implement (policies)[https://laravel.com/docs/10.x/authorization#creating-policies]:
- a new task
- a single task
- a list of tasks
- a list of tasks that are currently unassigned
- sorted lists of tasks
- a list of all tasks assigned to a single user
- dashboard including paginated, limited tasks
- assign one's self to the volunteer attribute of a task (
[volunteer](#confirm-organizers-and-adminstrators-only)) (all users)
- all fields (organizers and administrators)
Deletes a task completely.
This marks a task as cancelled but does not remove it from the system.
Indicates the task is done to be
confirmed by an admin or organizer managing a contract
Indicates the task was done correctly and (eventually) updates the status of the task as it relates to the contract
The only update action a volunteer role permits.
volunteer: Update a task to store the current user as the volunteer.
un-volunteer: Remove current user currently assigned to a task from the task.
assign: Store any user id associated with an organization to a task.
un-assign: Remove a user id associated with an organization from a task.
reassign: Update a task to store a new user id from a task.
I'm starting generally to make a point about dilution. There's been much of discussion lately about PHP Doesn't Suck Anymore, a title potentially offensive to people who never thought PHP sucked but generally embraced by many people who like memes and have ever been on the internet. PHP is not my favorite language. I didn't really choose to learn it. It's kind of gnarly. However, Laravel is definitely my favorite web framework. So as a person trying to learn PHP and a fan of sincere posting, I have seen this video several times. I also watched the primegean react to it live so I could get context and listened to several podcast episodes in which it's discussed. It's basically a list of things in modern PHP that are good with code examples and names of things so you can research them. If you really get into it and spend like 20 hours reading the docs on every feature listed (an example you know really happened because it's too specific to be made up) you can learn tons about how PHP is different from other languages and intuit what it's good for and some best practices.
I also spent several hours on DNF form alone because I love relational databases more than anything else in computing and as a consequence I love (beginner level) set theory and relational algebra. Then, I started looking into DNF types. Then, I started looking into how these are implemented in the C family and Java. Then, I went down a rabbit hole of opinion posts on reddit and quora and stackoverflow about why it's even good when languages are strongly typed (the top answer will surprise you). Then, I took all of this knowledge and made what I consider to be the best meme I've ever made in my whole life. Then, I sent it to my buddy Tyler. My buddy Tyler is my closest friend, teacher and career mentor. We have spent hundreds of hours chatting about code and tech and the like, so we have a pretty established language and understand pretty well what the other person knows about. He hadn't seen the video yet but he is a professional PHP developer and when he didn't immediately get this super specific meme I was absolutely shook. He has since seen it and now gets it but here's what I am trying to say: it's really difficult to say a complicated thing to another person when you know about it while respecting the amount of concepts and sheer jargon that make up a language or framework.
"The main dangers lie in the "unknown knowns"—the disavowed beliefs, suppositions and obscene practices we pretend not to know about, even though they form the background of our public values." - Slavoj Žižek
It is so difficult to distill experience into understandable technical communication that I can be totally blindsided by saying a thing that is clear to me and maybe 5 other people to a 6th person who isn't familiar with the exact group of concepts you are talking about. We contend not only with our own unknown unknowns but with our audience's unknown unknowns when we communicate and produce educational material and write documentation. That's why pest and especially the pest documentation is so impressive.
If you check out the pest documentation expectation API you'll get an overwhelming list of test cases that are extremely readable and applicable for specific groups of tests. That is a LOT of distilled testing experience just sitting there waiting to be considered. And you can bet that each of them have a use case because no one just make a random function, who has time for that? Nobody at PHPUnit, either, who have additional assertion docs which I found because I was reading Pest docs. Also: no matter how you feel about PHP, it is extremely popular. I heard Wordpress alone powers 98% of the web. I think this list is an incredible resource not just for writing tests with pest but also for understanding what is a useful thing to test for. Here's a bullet point version of some of "the many uses of tests" from The Missing Readme on pg 89:
- check that your code runs and behaves as expected
- protect code from changes that may unintentionally alter behavior
- encourage clean code
- force developers to use their own APIs
- document how components are to be interacted with
- serve as a playground for experimentation
- force a developer to think about the interface and implementation of their new program (as well as the dependencies they choose)
- serve as documentation
This is true of Laravel documentation in general. I'm really going out of my way here not to accidentally imply that Laravel got better recently after starting with that anecdote about PHP Doesn't Suck Anymore. One thing I've heard from Laravel developers in podcasts and in the conversations I've been privileged to have with some of them recently is this: read the entire documentation. This is getting back to The Curse of Knowledge territory again and I know it sounds dangerously close to 'just read the docs', but when I get the same advice from like 30 people in the span of a month I want to pass it on. I also recently got RayCast and have been using the shortcuts for tailwind and laravel and vim and let me tell you: peak experience. 10/10 highly recommend.
This is more about personal stuff than pest, but if you're still reading I'm glad you're here.
As a python developer who likes memes and have ever been on the internet, I had some opinions about PHP for a long time and let me tell you, they were not positive. During all those hours of talking to Tyler about code, I would tell him an idea I had for a flask project and he'd say some variation of 'yeah, that's a useful thing but Laravel already has it'. After like a year of this I broke down and started learning more about it and was really surprised about how much I understood intuitively about the packages and how they worked. I started learning Laravel to learn PHP in a way that made more sense to me after looking at frameworks in other languages, but I didn't expect how much it would teach me about how websites are structured. It's opinionated in so excellent a way it's accidentally broadly educational.
I've wanted to blog for a really long time, but I've never felt like I had anything to say that other people didn't already know. I've read many articles on DEV in the past decade and I wanted a DEV hat for a while. So after coding for a while as a hobby for myself for a while, I decided to try to meet people and maybe find a mentor. I got a DEV hat because I know the DEV community is wonderful and wore it to laracon because I heard the Laravel community is wonderful. But I was really not expecting how wonderful it is. I have learned so much and got to speak to so many wonderful people that my brain kind of can't handle it. And when I finally got it together to take up the challenge of working in public despite being super introverted and freaked out about it, it got reposed by some of those wonderful people almost immediately and I cannot express how included I felt or how rad I think it is that you're reading this.
And then I got a comment on my DEV post from someone in a DEV hat and I was like-- this is it. I've peaked.