This cli component was trickier to build than I thought
Vložit
- čas přidán 29. 06. 2024
- To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/DreamsofCode . You’ll also get 20% off an annual premium subscription.
This was much more of a challenge than I initially thought.
Normally, I would reach for a third party dependency when building a terminal UI component. However, this time I decided to write it from scratch, and whilst I learnt an awful lot, it was definitely harder than I expected.
Github Project: github.com/dreamsofcode-io/te...
Become a better developer in 4 minutes: bit.ly/45C7a29 👈
Join this channel to get access to perks:
/ @dreamsofcode
Join Discord: / discord
Join Twitter: / dreamsofcode_io - Zábava
To try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/DreamsofCode . You’ll also get 20% off an annual premium subscription.
Nice walkthrough of TDD in Go! To be nitpicky: your frameRate should correctly be called frameDuration. "Rate" implies a number of frames per unit time, so the inverse of your value.
Thank you! It's a good nitpick, and I def agree with it.
Yes you have to pick the nits or they'll grow up into adult lice.
I love that you added how you came up with a solution and also the ones that didn't work and why they didn't instead of jumping to writing the working solution!
agreed
i feel like this video was made just to show off the keyboard with the amount of shots the keyboard was in
This video was certainly a little different! I've been getting inspired from some other creators in different niches and wanted to try some more story telling this time. Mostly, software development is typing on a keyboard though 🤣.
I thought the third party package was a good shot though.
@@dreamsofcode the package shot was so good
@dreamsofcode You need to introduce us to your Moonlander. I wanna know everything. Your cables (Kingscables?). You modifications. Your platforms. Spill the beans! :D
Deal! I'll add a video to my backlog!
"Wake up babe, new dreamsofcode vid just dropped!"
I learned Go entirely self-taught from reading source code on Github and I've picked up pretty much all of these conventions and patterns, but I was unable to put a name to any of it. This video helped all of that finally click.
That's awesome to hear! I'm glad it was helpful.
Wish there were more detailed videos like this one. Usually development project videos describe the process the author went through in very broad strokes. On the other hand, development streams being unedited makes it very inconvenient to watch them. The format you're going with is the perfect one.
You could also return a "done" channel from "Start()", which would be closed in a defer inside the work goroutine. That way, you can still use context, but you can wait on the returned "done" channel for work to be completed, after cancellation, before continuing. I much prefer this way, as it doesn't require any state, and can be implemented in just a regular function. No need to have it be a method on a struct. It's also easier to avoid race conditions (because of the lack of state).
func DoWork(ctx context.Context) (done
Also, when you want something to be repeated on a schedule, like every 50ms, you should use time.Ticker instead of time.Sleep. If your work takes any reasonable amount of time, with time.Sleep you'll slowly drift away from the "every 50ms". You can also use time.Ticker's channel inside a "select" statement, with your context cancellation. So, you don't have to wait for the time.Sleep to finish before your context cancellation is received and acted upon. EDIT: I hadn't made it to the end of the video before I made this comment. My mistake!
I thought TDD stood for "try driving drunk" and I thought, why would anyone do that? now I know, its way better
So after the video you know driving drunk is even better. Ehat a god vid
you make nice videos mate
It depends on the project.
Many projects simply aren't playing nicely with TDD.
TDD is more like TDB. try driving blind
@@JarrodMedrano It's a good idea to assume what you want your API to look like, but often you realise half-way that some things can be made safer/faster if the API was slightly different.
I had always wondered about the loading animation and now I've a good idea about it's working. Will try to implement it at some point. Thank you :)
really nice well explained and edited video. will be surely applying that pattern in my next go project
The longer I watch the more I want your vimrc 😅Nice video! My dad keeps recommending Go to me and it looks fun so I might give it a try, the pacing and really everything about it make it really nice to watch as well!
Nice video as always !
I worked with Go daily at my job for 2+ years and I personally wouldn't use a Context in such a case. Contexts are nice when you need to propagate state and cancellation events through a chain (or a tree) of related function calls. If you only need the cancellation part, just use a channel (that's what Contexts use under the hood anyways). The same advice applies if the thing you're trying to achieve has a small, well defined perimeter (like a single struct in your case).
You could also get so your spinner to stop properly with a single channel, I wrote a simple example for those interested:
package main
import (
"time"
)
type foo struct {
stopCh chan struct{}
}
func (f foo) start() {
tick := time.NewTicker(time.Second)
go func() {
for {
select {
case
Great walkthrough, if not one of the best on how writing test driven code works!
Nice video to see some real world examples for go programming. Thanks Elliott! Also, I really like your dotfiles and nix config :), I hope you will do a Video on that topic soon, I had some major mental breakdowns settings up nixOS ^^
Thank you! Definitely coming soon! Hopefully I can make it easy to comprehend because it can be a lot!
I feel like there are too many subjects in this video: how to make a spinner (and why some things don't work), how to do tests, and how to do all the details in Go.
I was interested in the spinner-aspect, but was completely overwhelmed by the Go-stuff (don't know the language).
The test-concepts you touched on were also interesting, and could deserve a more higher-level video.
In the end, I don't know what I saw you all do. You shared a story about how you struggled and succeeded coding something, but I don't really know what were the exact problems were with the spinning aspect, and how you solved it.
I would love 2 more videos about the spinner and the test, where you go talk more abstractly about those subjects (no/minimal programming-language used), because I'm really interested in both!
Hey! Thanks for the feedback. This video very much was more of a story rather than trying to teach anything concrete. I'm always playing with different ideas for videos and this was one I wanted to try.
I'll definitely do some more dedicated videos on each of the subjects though as I think that's also valuable.
@@dreamsofcode As a story video it was definitly a good one.
I can't really pinpoint why I expected something different though.
Thanks for the look into a cool project, and a great intro into TDD :D
I think the most important part of TDD and writing just enough code to get the test to pass, is to make sure the test is sophisticated enough to ensure the added code isn't just trivial or hacky.
Oh I love that you opened with a shot of your gear on your desk !
Loved the video and the showcase
Thanks for taking us on that journey. going back to paper is shown seldomly and jet so effective.
When you ended with "now I did git init" I was a little surprised. Every time you hit "green" and after each "refactor" my brain did a "git commit -am '!sqash init'"
Maybe I'm just too bad at rolling back my changes manually or maybe there is something I'm not seeing. Could you explain why you did the init at the end and not at the start? (esp. no commit after each step)?
thanks again =)
edit: Already answered in another comment: Git was added as an afterthought.
Thanks!
I agree, git should have been there from the beginning, but having worked on the original approach and trying again with TDD + turning it into a video, I definitely forgot to do so.
More practice with TDD needed on my end!
Great video!, it would be cool a video about programming books/resources recommendations
liking this video format a lot
Very interesting. Thank you.
Love the new filming style!
Thank you!
I have to admit, I've never written a spec of go, but I still learned a lot from this video! Keep it up, @DreamsOfCode
Great video as always, but finishing the spinner and then using git to store the code instead of actually using the vcs and committing in the red, green, refactor process would have been a better way of utilizing git
You are 100% correct. Git ended up being an afterthought as I was building it out. Def one of my bigger weaknesses.
@@dreamsofcode Same here. I often find myself bringing git in as an afterthought, when I should be noticing "This is a new Project, therefore I need git".
Thank you,
I followed you along, it was a big fun for a Go-newbie.
couple of moments:
1) it didn't work for me on FrameRate 20 - it was too unstable.
It wrote at about 30ms rate.
2024/07/03 18:55:52 Before Start 18:55:52.834
2024/07/03 18:55:52 Writing frame: - at 18:55:52.835
2024/07/03 18:55:52 Writing frame: \ at 18:55:52.865
2024/07/03 18:55:52 Writing frame: | at 18:55:52.897
2024/07/03 18:55:52 Writing frame: / at 18:55:52.928
2024/07/03 18:55:52 After Stop 18:55:52.943
tests are ok on FrameRate 200 for me.
2) once you use s.Stop() just before defining it, which is a bit confusing, but ok
Another thing is - if i comment out log outputs in the main.go then there is a symbol \ left from the spinner after main is finished. (it is ok with additional log outputs)
❯ go run ./spinner/example/main.go
\
Wow the presentation is soo good
Great video on TDD. I feel the "context" approach is a bit complicated for this kind of project.
Btw what kind of cable are you using to connect your moonlander? - looks nice and practical
Great video!
the timing of this getting uploaded while i had your CLI stream playing on the background haha
🤣
I understand it meant to be an educational video about TDD and how to approach to coding projects, but it's a lot of code for such simple task. It's basically few lines of code in any verbose scripting language.
@@barterjke even in C it would be few lines of code
The amount of lines actually has nothing to do with TDD, he is just writing code in a reusable, maintainable way
Great video as always boss
Thanks home slice!
What's the model of your keyboard ?
Hi I would like to learn how you make your videos. Can you guide on what tools and softwares you use?
Nice work. Showing the final spinner a few more seconds would be appreciated, though.
I did not get the last part about making the spinner thread-safe. In the Start method the locking is done around context creation and done channel instantiation. So if the Start method is called simultaneously from multiple goroutines they are going to skip over isRunning check and then create same context and done channel. I would wrap the isRunning check in with a lock because I do not see how the spinner can be used from multiple goroutines. Am I missing something?
If there are multiple threads in the application than the Start funcion is racy with itself. The spinner can be started twice at the same time, e.g when both execution contexts (threads) check isRunning and loose the processor right beforr the s.lock.lock(). After that, the code inside (and after) the lock, which includes starting and configuring the Spinner, is executed twice.
To fix it You can just add a check isRunning -> return when you already have the lock acquired.
Edit: commas
PS: I am not familiar with the golang execution model (can there even be multiple threads?) so this comment only applies if multiple execution context can call (or rather execute) the same function on the same object at the same time.
You are correct, there would probably be a race condition in that case!
I need to know your whole OS Setup
Looks like he's using Hyprland as the window manager... Would also love a video like that
He has another channel called dreamsofautonomy
Well explained video. Can you share your tmux config?
@dreamsofcode can you make a video on youe terminal and how we can make one of our own? Im a student and i am sure toms of other students dont know this stuff, the very little makes it confising how to do such stuff
0:42 wow that wallpaper is absolutely amazing, where would i be able to find it?
I get all my backgrounds from freepik! I'll add a link to it when I'm at my desk :)
@@dreamsofcode Up, I really like the wallpaper too :D
Seeing you and primeagen flaunt their ergonomic keyboards is temping me to check one out (although mechanical keyboards are a little bit overrated imo), so what keyboard is that?
It's the ZSA moonlander! I've tried a few and this one is probably my favorite. I'll make sure to do a video on it soon
Moonlander is great! Extremely easy to program custom key maps too.
Get the iris instead. The moonlander thumb cluster is too big and unwieldy. Ben vallack says this in a vid he made
Cool wallpaper!
Nice vid! Wonder if there's a way of mocking the ticker in order to not have to sleep in the test.
Thank you! And I believe so. Not possible with the time.After func but def possible with the ticker
I feel like it doesn't make any sense to run the spinning wheel in parallel because it doesn't give you any information about state of the main thread. The spinning wheel should have been incremented after every operation in the main thread so user can see if the main thread is still doing something.
What keyboard are you using?
This is a really good demonstration of TDD, although my one complaint would be that the first test should have been written once you’d come up with the design but before implementing anything, even if you were calling not yet defined functions.
Your hard work really pays off
this is a cool introduction to golang's concurrency pattern
i rly love the start window animation. Is that a copmositor or just video editing?
Both! I have my hyprland animations set to how I edit my videos now! You should be able to find it in my dotfiles.
@@dreamsofcode nice but ahh hyprland… alright I use bspwm(I tried hyprland and found it troublesome). But ty for sharing
Where do you show the visual of what you coded/were trying to code?
Unfortunately I had removed the code 😔. I wasn't expecting it to turn into a video and so I didn't record my original work.
This might be the first time I've ever left a comment on a video having only watched 15 seconds of it but... This is one SICK Moonlander setup. Makes mine look like a scrub made it...
what keyboard is that?
You explained TDD better than my professor at college ❤
Is that a moonlander? How are you finding it? I’m using a custom kyria split keyboard at the moment and keep eyeing up the moonlander 😄
It is a moonlander! It's my favorite of the split keyboards so far, just beating out the voyager. Mainly because I use the extra thumb key for video editing.
If it were just pure coding, then I think the voyager might be better, but missing a wrist rest is upsetting as well.
I'll do a video on each soon!
@@dreamsofcode I can’t say I’ve heard of the voyager, I’ll be sure to take a look now, thank you very much 😄
Looking forward to your review video
What theme is that?
This is like fizzbuzz enterprise, except the irony is gone.
New keyboard ????
"Little bit confused" we have all been there..
amazing
Hi Can you give me the link to the keyboard you are using.
Hey! It's the ZSA Moonlander! I don't have a direct link as on the move but google should.find it
Curios to know how long the entire process took though 😅
For TDD? Definitely quicker than the initial approach (especially as it ended up working) but I have decided I wanted to capture it as a video at that point which always adds time.
Compared to using a third party package then yeah def slower haha
it would be interesting to see this done in haskell
Agreed! I think it's an interesting project in general due to the concurrency and thread sync.
I'd love to be a CLI designer. I have ample experience in MUD coding. But alas it's not an actual job
the fact that the commit came after the app was done...
Do we all do that?
I already hear my ex-colleague screaming and calling meeting because this is not how you are supposed to do TTD. You need to follow enterprise patterns as laid out in 5 academic papers he kept quoting.
It seems so complicated to do something that easy... Also using TDD for a UI tool seems really not adapted at all. Here it is so simple that doing test seems possible but for any more complicated UI, test are not adapted, too complex to write, breaks at any changes in the UI and make you loss a lot of time.
It's called a "throbber" not a "spinner". 😂
Ngl, at first i thought you were on a cockpit, then realised it's just your setup....
It's called a throbber
while the presentation is top notch, some minor complaints about the TDD shown here:
- lots of code written before the test
- test tests implementation details not behaviour/intent(which should drive the design)
- you skipped the red in red-green-refactor altogether
tdd should drive the design and not be a implementation validator. as a result you should end up with a better design that is as a side effect also easier to test. so tdd should be better called test driven design imo. do not want to sound overly pedantic here but i reread "tdd by example" recently, so it was fresh in my mind.
fmt.Println("Loading...")
should of just added a jif
My God. So much work for a simple spinner. No wonder I never see apps in this language. It takes 5 years for a simple Hello World program!
I feel like this is an example of why tests are not as important as actually making sure your code runs as expected, as you're writing it -- even though your tests passed, there was still a serious bug in your example implementation. The tests could have been added when they were necessary; instead, you arguably spent more time fiddling with tests, rather than iterating rapidly on a solution to the bugs that would've appeared, simply using your example implementation. I'm in favor of the approach: get it working as expected, refactor (or rewrite), write tests, fix any edge-cases, write final tests.
I have to disagree.
TDD made the implementation a lot more simple, focusing on what I wanted the code to do and validating that it was doing so.
TDD is very much a way to write code in the most simple way possible, and by focusing on tests from the start, you make it easier to incorporate them than "fiddling" at the end.
This looks okay until one change causes regression in another edge case
keyboard??
ZSA voyager!
Oops I mean Moonlander lol
This makes me grateful to be a dotnet engineer
TDD is as insane as it sounds, I could never write software like this.
TDD is great.
It's certainly very disciplined and not normally how I go about writing code, but in this case it made it easier to build out the component.
Def something I'm going to consider more moving forward to be honest.
I feel like you're using TDD wrong here. The idea is to reduce things to input and output, but in that case, you can't really test visual/external output like what terminal is doing.
And to me the important feedback TDD gives is, make this untestable part as small as possible, and make clear boundary between that and the testable "inner core"
Here, to me it seems like the code tried to, imperfectly, capture the "whole thing", without this boundary defined so you got these weird problems as tests fail when you realize this unknown boundary actually is different than you thought, giving tests that are hard to reason about in terms of, what are they really asserting.
Me, a go binner created this "code". It displays a clock in rainbow colors:
package main
import "fmt"
import "time"
func main() {
ascii_characters := []string{ "\033[31m🕛\033[0m",
"\033[33m🕐\033[0m",
"\033[32m🕒\033[0m",
"\033[36m🕓\033[0m",
"\033[34m🕕\033[0m",
"\033[35m🕗\033[0m"}
for (true) {
for _, value := range ascii_characters {
fmt.Print(value)
time.Sleep(50 * time.Millisecond)
fmt.Print("
\033[?25l")
time.Sleep(50 * time.Millisecond)
}
}
fmt.Print("\033[?25h")
fmt.Print("
")
return;
}
Here before yugal
Neovim btw
super duper dumb ways and impractical way to do this:
1. pseudocode
2. write in python
3. rewrite in c
4. rewrite in rust
5. rewrite in lisp
6. rewrite in brainf*ck
7. rewrite in every single language
No more nvchad?
Ah, the old "Bait the hook with interesting topic, then Switch to TDD evangelism!" 😂
Good video, nonetheless. 🤷♂️
This is test driven development done properly. One hell of a lot of devs claim to use TDD because it sounds cool and is a buzzword. They're not actually doing it.
Thank you and I agree. I really enjoyed using it for this project as it def helped me think about the complexity.
second
Is this meant to be a joke? Sorry I'm American I don't get it . 😂
You quit Rust?
I still use Rust, just for different things.
The CLI tool I was building is only able to be integrated with Go at the moment.
first
You sound so monotonous, someone reading the script, no emotions!
Great video!
What keyboard do you use?
This video was the ZSA moonlander which has probably become my favorite keyboard alongside the voyager. The moonlander is slightly better for video editing as well due to the third thumb key
What keyboard are you using?
In this video, I used the ZSA Moonlander!