Mr Prime I was on the fence about studying CS as next career. I’m a 43 yo industrial automation engineer. My industry is moving towards more CS principles. I have been watching this trend for at least a decade but have resisted (many of our systems stick around for decades). After watching your videos I feel invigorated to embrace my studies. I’m working towards a BS in CS now.
Assertions are the most amazing thing to ever be created. If you take the time to write down meaningful assertions with meaningful error messages, it lets you know exactly what went wrong when your program malfunctions. No need to debug anything, the program just tells you "Hey something went wrong! This function line 450 in that specific script was expecting a strictly positive integer but 0 was given". And in the rare case where you actually forgot something, you'll have to debug, find the root of the problem, and create an assertion for it to ensure no one else ever has to deal with debugging that in the future.
I agree. And it is fun to type too because usually you can write it in just one line (1 liners always feel awesome) (even though currently I mostly use them for testing and not for prod)
Why can't the if statements do the same thing? why does it have to be assert? Unless assert is just us calling anything that makes sure the state of a object is as we expect. Doesnt matter we use if or something else.
@@ark_knight In most languages, assertions are stripped when you compile the project in release mode. So you get to have asserts in your production, but there is zero overhead for clients. You could have an if statement with a log inside, but that means you always divert the flow of your program at runtime, which is not what asserts are trying to do.
@@felixp535 But if your aim is to crash the application anyway, then does it matter if it changes the flow of the program right before crashing? Perhaps it is something I need to see in practice to understand.
I love the approach. It's a genuinely good thing to have assertions that bail out before the state goes sideways and you have to figure out the root of the problem much later. But this is why strong typing is also good. You can make situations like calling disconnect on a client that hasn't connected completely unrepresentable in your code with a typestate pattern. There will probably always be state that you can't just type your way out of testing, but you can at least focus your testing to those areas.
I was wondering this as well, why is it DummyClient and not ConnectedClient 🤔 Maybe I don’t understand. I like the idea of simulating and preconditions with a seed and logging though.
Because it is still not enough to eliminate the error. The closed client can still be mistakenly re-closed and connected ones can still be re-connected. Without linear types, assertions (something of runtime) are needed .
@@ChosunOne the type state pattern falls apart when your system is not just a client that can be Connected/Disconnected. If it is possible to just enumerate all possible states in a finite amount of types, then the program is trivial.
The problem is when you have any type of FFI at all or casting is possible in any way then the types can fall apart at runtime without you knowing, a runtime check that nothing went wrong in the underlying layers is where asserts shine
@@youtubeenjoyer1743 It doesn't fall apart just because you don't enumerate all your possible states. It can still be used to restrict certain state regimes and limit the amount of asserts/testing you need to do.
@@chbadev the OTP/Erlang/Elixir style of programming is to let things crash if they don't work. you can do this because on OTP you can spawn millions of threads that are independant from one another, so crashing one doesn't crash the entire program. Further OTP allows you to inspect running code in production to see what's going wrong, as well as do live updates.
For the ones saying "why crash your server in production, that sounds odd". He mentioned he's using the style TigerBeetle is using. That style was created by NASA to ensure there's a very low chance of a bad bug happening in a place where it can't be fixed. (you don't want a rocket falling out of a sky because of some obscure bug). So asserting is forcing you to approach the code in a different way if something that logically never should have happened happened. (like disconnecting a client that isn't connected). TigerBeetle does also keep the assertions in production.
The example Prime is giving is about avoiding stupid errors while coding. In his example, he’s responsible for the connection’s lifecycle, so it makes sense to assert it’s open when you’re trying to close it. If there was a way for something else out of his control to make a connection not open, sort of a rug pull, it would make sense to do it some other way, like pretending to close it anyway but emitting a warning.
That makes it even more concerning, not less. Why would you want rocket code to crash when it tries to close a connection which is already closed instead of just doing nothing? During dev, sure, do whatever, but replacing ifs with asserts just makes prod more brittle.
@@az8560asserts are used for testing that your invariants hold. Invariants are things that MUST always be true. If your invariants do not hold true, your program has entered invalid state. From there lots of things can happen. Some of them potentially dangerous. Using asserts helps you be sure your invariants are uphold. And if your program enters an invalid state in pro (which should never happen) it’s better to crash than to potentially cause more trouble. Anyway, if you’re program is in an invalid state, it’s gonna either crash or produce bad results which you wanna avoid.
And if you think assertions in a long running service are bad, that’s because your thinking they will terminate the program. Generally, assertions raise exceptions that will be catch by some piece of code (like your http server library) so that request in gonna fail but not others. In languages like go, assertions will panic, but you can recover from it (for example i think the stdlib http server already does by default). Even if your program really crashes it’s not a problem because it should be run in some managed way that would relaunch it if it terminates (like rc scripts, systemctl or docker).
@@az8560for example in a software that does some kind of (monetary) transactions, if your discover at runtime your state is invalid, you return a 500 server error. Does not make sense to proceed. You can add custom error handling code to return a nice message or something but most of the time it’s not important and it’s easier to just say THIS MUST HOLD TRUE IN ANY POSSIBLE UNIVERSE and if it’s not the case, which should never be, just fail.
The way that mr. prime teaches is honestly amazing. I must aim for this way of understanding what I'm learning. Since my algorithm design class is killing me.
I added an assertion the other day, and it correctly identified a missed assumption, but the assertion was in the middle of some code which had mutated parts of an entity, and then we ended up with data which was half-saved and incorrect after the assert. Boss said "i don't want to see another assert ever again". On the one hand, we are now not able to assert that some behavior is correct, but on the other hand if you have a hot path used by millions of customers, you don't want that to fail. Something you can do instead is to create an assert function which throws in development, and only logs a warning in production, if you are afraid of creating downtime/extra errors.
what is Praying mantis style? A part of your code just pretends to be a branch, but when a bug appears, it reveals its predatory nature and devours it? (I actually think you are probably just joking that you are praying for you code to not crash... but I can't stop the thought)
@@NotGarbageLoops you mean praying mantises being enemies with tiger beetles? never heard of them being natural enemies. why would they be, they hunt different prey in different places?
I’d love to see more content on how you construct your classes/functions to be “unit testable” without needed anything like mocks. I’m so used to the DI/mocking style of programming that I can’t even understand any other way to construct the code so that you don’t need mocks to test.
What you are describing here is called Offensive Programming. The "if conn != nil { ... }" approach is Defensive Programming. Totally agree that Offensive is generally better than Defensive. Great example!
Can also be offensive, if you write if conn != nil { panic() } The difference is that the defensive one handles error, while the offensive one throws error.
This is called "Design by contracts". Also lot of cases with mathematical software proofing this technique is used. Maybe try Eiffel or Altelier-B some day? Very interesting languages, Altelier-B being more enlightment and Eiffel being more approachable.
This is how I always saw my coding projects yet inexperience kind of forced my hand to not be so thorough and try to make things work and move on. If it doesnt work thoroughly and predictably it needs to be handled to the point of minimal error. I think we need more of that in game dev in general since it would help with understanding and fixing issues a lot faster and more concise. I mean what you were saying about how you could just put an if statement that would just verify there is a connection that needs to be disconnected but not being able to call disconnect in the first place made prevent other issues that werent foreseen since the proper validation was authenticated. Good stuff Prime cant wait for you and Thor to make a game together lol.
You're gonna love branded types. Compiler literally doesn't allow you to pass in a not connected client. But obviously those two things go hand-in-hand
@@sohn7767 Everything is more than one type. A mug of coffee can be of the type of cup, or liquid container, or looking it from a geometrical mathematical point of view, it can be a type of donut. Or a type of beverage, or type of item on a restaurant menu. What would be the type or parent type of this mug of coffee that works in all contexts across our codebase? Or do we have multiple types for the same thing (ouch)? Maybe all the possible complex connections could be described by types, but it adds a lot of rigidity to the code. I think a better approach is to let data just be data, facts that are either there or not. By decoupling data and functions, it makes it easy to add more functions without having to redefine and recombine a collection of attributes or facts into a single type that makes sense in all contexts. Types certainly have their value in certain contexts, like at the boundaries of an application. Organization (like pigeonholing everything into types) is just a tool which in itself doesn't reduce complexity.
@statelessdev you argue that a mug can be all sorts of different types -- which is absolutely true. But a mug looked at as a geometrical object is very different from a mug looked at as a container, and we need different information about it depending on how we're examining it. So yes, there absolutely should be different types depending on how we're looking at it. Let's talk about a real world example. Having multiple types for the same thing is not as much of a headache as you portray it to be. With refinement types, you can add a predicate that "d.conn != null" (or more realistically, "is_connected(d)". Then the type system can figure out when that's known to be true and when it needs to be checked before calling the function. Or we can use things like GADTs to index our client by a type level value indicating whether it's connected. We call connect, and at the end it says the client is now the connected type (FStar and Idris do this with mutable references, though most GADTs are used with functional programing). And I'm mostly familiar with how functional languages do this; there are other powerful imperative languages like Dafny that offer similar techniques.
I love this because it's a great combination of analytical and empirical and test-driven approaches, but it's not TDD, which I love in theory but not so much in practice.
I like this a lot but like others I'm uncomfortable panicking on production for an insignificant state. Instead, you can both assert AND check the connection != nil, and modify the assert function to panic in development and log a specialized error in production. That error log, ideally, should be mostly empty so any errors that show up will be noticeable. Although the more I consider it, I wonder if logging is best for both production and dev/simulation, to capture multiple possible states? Again, the log file should be empty, so any entry that shows up is important.
You do whatever floats your boat. Its your program afterall. And unless you know how to run vast number of simulation and fuzz testing, you will never be confident in what your are building for prod. Devs who has really thought through EVERY SINGLE SCENARIO, and are confident nothing can break their app. In those cases, the asserts will be great because it will force you to take action when the application does really breakdown in one wild scenario which the dev couldnt conjure himself. But you have to be confident that those will be rare.
Been long loving the idea! Assert your invariants, and try to use type system to make invalid states impossible to represent. It's not trivial, but I feel most of us don't take full benefit of type system. It goes way beyond simple "is this an int or string"
Man I’ve either missed the streams or you never actually deep dive this build but man seeing a DST style build for a project as you build it would be sick
Hoare logic is from the 1960s. Dijkstra wrote a whole book on using this style in the 1970s. Meyer developed the Design by Contract style in the 1980s. There's nothing that computer programmers love more than reinventing the wheel.
With my Script2 API I use C++ macros for my asserts that do different things in different models. D_ macros run at debug-time. R_ run at run-time. A_ run at debug and run-time. C_ is configurable to run at run-time but always run at debug-time. Script2 uses the Chinese Room Abstract Stack (Crabs) machine, so it's easy to save the exact call to bugged functions by the nature of the stack. We also don't have any problems with memory safety with our all contiguous design.
I think the most important thing is the TYPE of assertions. You don't want too many low level assertions. Things subject to change or things that could possibly change. It's the same thing with unit tests. Too many (and dumb ones) make it unnecessarily slow to do anything in the future.
I'm so happy I'm not the only one who try to prevent very rare errors. I remember many years ago, I've decided to create a 'Very Crazy Error' message. All the values in the subroutine were dealt as positive integers. 'Very Crazy Error' was printed on stderr only if the integer was negative, which was impossible. Well almost. Many years after, somebody created a sub that called my former sub, guess what, every thing seems to work until... The program crashed with a 'Very Crazy Error' as the only error msg available. I told them to grep -n 'Crazy' in the source code. They fixed the error in three minutes, after a little brainstorming. I was proud of myself. More than 10,000 lines of code in that module... (Today I would describe the error better.)
Slither is built like this, it worked fine since it's a locally run program. For a production app, I feel like it makes more sense to log out these errors instead of basically DOSing your live app. Although, I could see why force crashing the app would help a fuzzer quickly assess that something went wrong and state needs to be captured. Fuzzing doesn't catch all bugs, though. Maybe a flag to toggle between asserts and logging makes sense?
Yea, ive been loving asserts recently. My main annoyance with them is how pretty much all standard library assert implementations will be stripped from optimized builds. I get that it was probably a carryover from the early days of programming when it was costly to leave code in that you expected to never hit, but nowadays, how do you expect to catch any bugs when all the checks are gone in the production build? The users are guaranteed to be testing your software way more than you are.
This sort of programming is so painful, constant error checking ... reminds me of my C coding days 30 years ago ... have we really gone nowhere in that time?
This was quite interesting, I've actually found myself on a somewhat similar thought path for the past few weeks. At my current job I work on a large website that has been built and maintained by multiple past and present employees so I decided to work on a mobile app outside of work to keep learning and trying a different type of coding experience. I'm very used to wrapping everything in try-catches and validating within each method that the call was valid that I started building in that style within my own app. But then it occurred to me, why would I ever call these methods in the first place without the necessary prerequisites? And with state, why would I have nullable attributes on my types when I know that at certain point these values are required everywhere? So that got me thinking about instead trying a different approach, as I'm the only one working on my app it'll obviously be easier to be strict with usages and attribute setting on my types. I'll certainly be trying asserts throughout my project, that makes more sense to me and I've never really liked having to rely on mocks anyway.
Guess I am on the right track. I just kinda think this way. I assume the same rules as math, i just adjust the sentiment of the acronym. Things just have to run in a certain order, which seems plainly obvious to me. Testing my own flaws is a given. It's just the way I am wired. I love to build things... just to see how i can break them.
So just so I understand, you're saying that it's better to throw errors earlier in the program's runtime than later on? Makes a lot of sence. So like, if some program typically runs in such a way that functions "A,B,C" typically happen first before, "C,D,E" (assuming they share data). That debugging and error checking is easier when you add asserts to functions "A, B, and C".
Being doing the same and it's amazing. One thing that I haven't seen talk a lot though is how to do the simulation and fuzziness. Would be cool if you could share that.
The title made me think of an interesting video. Could you do a video where you review some code you wrote when you were a junior and do a roast on it.
this is really great advice. but just to be clear, this does NOT mean "do not unit test". if you only rely on this practice you expose yourself to "burn-in" periods of fuzz testing, and at some point you need to decide if you waited long enough to consider the system stable. that's not enough. the system is not right if it doesn't crash for a month. but having unit tested all possible edge cases from day one is unreasonable for most problems. do both. inform unit tests of crashes happened during fuzz testing.
Pretty nice for catching Heisenbugs ahead of time, but hard to prove to management that it's worth it 😉Wondering how it would hold up against a "current" (recently discovered) Heisenbug.
at 1:25 you can just indent inside the brackets with >i} instead of entering Visual mode
หลายเดือนก่อน
Can you do a what is simulation and how to do it properly type vid? Like with a minimal example? It's hard to do it right and it would be great to hear your thoughts on it. Thanks for the video! :)
How is this different to if err != nil { return err } The error will be returned upwards and will be handled by whatever monitoring system you have and you will be alerted. If you want to add some extra information you just create a custom error type
I've been playing with similar ideas at work and it does seem to work well. The only issue I've had is in dynamically typed languages it significantly ramps the cost of change. I love it in c++ but found it miserable in python.
Tried using this approach a bit in Java in my job and also felt it was a nice approach, but sonar complained when I tried to merge my code. I might have another look to see if I can use assert
You should not. Assert is for test code, not for production code. I work with saas environments that won't even let you deploy with asserts because it is a flawed programming style. Also asserts overhead is not worth it. What he's doing here is having his code also be his test code. That's not good
@@mattymattffs "that's not good" tell that to all of the heavy users of Erlang/Elixir. their entire programming style is letting things fail in prod. and it ends up making some of the most resilient systems that we currently have. (phones, discord, whatsapp)
The Golang standard would be to return an error. Asserting in a "test assertion style" throughout your code is not good practice in Golang. Panics are nearly always a code smell, not a guardrail. Why not write an assertion library that returns an error? `if err := assert.Thing(); err != nil { return err }` You could even have an array of assertions and check them all with one line. `a := assert.Many(assert.Thing) ... a.Add(assert.AnotherThing) ... if err := a.Assert(); err != nil { return err }`
asserts everywhere sounds like javascript programming, some do a lot of asserts as well... :D looks like prime is rediscovering how javascript programming works :D
In most languages (PHP for example), the default setup disables them in production for performance so you can leave the assert() functions in the code and the production server completely ignores them. For years I didn't know that so avoided them as the thought of commenting them out/in every time I made changes was horrible.
I'd love to see a more comprehensive video on how you do the simulation bit.
+11
same, please.
+1
Same
I’d love to see 12 years a slave
It would be great if you made a vid or a simple project showing how you wire up the fuzz and simulations. That’s where my journey usually fails.
Agreed
This. I think everyone has an idea on how this could be implemented, but seeing someone actually go through the process would clarify it immensely.
Mr Prime I was on the fence about studying CS as next career. I’m a 43 yo industrial automation engineer. My industry is moving towards more CS principles. I have been watching this trend for at least a decade but have resisted (many of our systems stick around for decades).
After watching your videos I feel invigorated to embrace my studies. I’m working towards a BS in CS now.
Good luck!
This comment is sort of ironic because he didn't learn this from CS, he came up with this technique as a way of dealing with painful debugging.
@@NotGarbageLoops He said videoS, not this specific video.
43yo junior dev here. There's no better time than now to do something you're interested in, BSc or not.
BS in CS hehe. i wish i had a real engineering background like you.
Assertions are the most amazing thing to ever be created.
If you take the time to write down meaningful assertions with meaningful error messages, it lets you know exactly what went wrong when your program malfunctions.
No need to debug anything, the program just tells you "Hey something went wrong! This function line 450 in that specific script was expecting a strictly positive integer but 0 was given".
And in the rare case where you actually forgot something, you'll have to debug, find the root of the problem, and create an assertion for it to ensure no one else ever has to deal with debugging that in the future.
I agree. And it is fun to type too because usually you can write it in just one line (1 liners always feel awesome) (even though currently I mostly use them for testing and not for prod)
the most amazing and the most underrated thing too. so many missing out because they think testing is boring
Why can't the if statements do the same thing? why does it have to be assert? Unless assert is just us calling anything that makes sure the state of a object is as we expect. Doesnt matter we use if or something else.
@@ark_knight In most languages, assertions are stripped when you compile the project in release mode. So you get to have asserts in your production, but there is zero overhead for clients.
You could have an if statement with a log inside, but that means you always divert the flow of your program at runtime, which is not what asserts are trying to do.
@@felixp535 But if your aim is to crash the application anyway, then does it matter if it changes the flow of the program right before crashing?
Perhaps it is something I need to see in practice to understand.
This is definitely a style that requires some time to get used to.
So nice to hear you so excited about it, the excitement is contagious
its been going great! the asserts are wild, but hey! i have been able to find so many really hard to find bugs
I love the approach. It's a genuinely good thing to have assertions that bail out before the state goes sideways and you have to figure out the root of the problem much later. But this is why strong typing is also good. You can make situations like calling disconnect on a client that hasn't connected completely unrepresentable in your code with a typestate pattern. There will probably always be state that you can't just type your way out of testing, but you can at least focus your testing to those areas.
I was wondering this as well, why is it DummyClient and not ConnectedClient 🤔 Maybe I don’t understand. I like the idea of simulating and preconditions with a seed and logging though.
Because it is still not enough to eliminate the error. The closed client can still be mistakenly re-closed and connected ones can still be re-connected.
Without linear types, assertions (something of runtime) are needed .
@@ChosunOne the type state pattern falls apart when your system is not just a client that can be Connected/Disconnected. If it is possible to just enumerate all possible states in a finite amount of types, then the program is trivial.
The problem is when you have any type of FFI at all or casting is possible in any way then the types can fall apart at runtime without you knowing, a runtime check that nothing went wrong in the underlying layers is where asserts shine
@@youtubeenjoyer1743 It doesn't fall apart just because you don't enumerate all your possible states. It can still be used to restrict certain state regimes and limit the amount of asserts/testing you need to do.
He's so close to discovering Erlang/Elixir.
Had to go backwards and learnt Go... Maybe his hate of HOF will end someday once he stops doing them in JavaScript
Yes the haskel world
haha big true
curious, can you elaborate?
@@chbadev the OTP/Erlang/Elixir style of programming is to let things crash if they don't work. you can do this because on OTP you can spawn millions of threads that are independant from one another, so crashing one doesn't crash the entire program.
Further OTP allows you to inspect running code in production to see what's going wrong, as well as do live updates.
For the ones saying "why crash your server in production, that sounds odd". He mentioned he's using the style TigerBeetle is using. That style was created by NASA to ensure there's a very low chance of a bad bug happening in a place where it can't be fixed. (you don't want a rocket falling out of a sky because of some obscure bug). So asserting is forcing you to approach the code in a different way if something that logically never should have happened happened. (like disconnecting a client that isn't connected). TigerBeetle does also keep the assertions in production.
The example Prime is giving is about avoiding stupid errors while coding. In his example, he’s responsible for the connection’s lifecycle, so it makes sense to assert it’s open when you’re trying to close it.
If there was a way for something else out of his control to make a connection not open, sort of a rug pull, it would make sense to do it some other way, like pretending to close it anyway but emitting a warning.
That makes it even more concerning, not less. Why would you want rocket code to crash when it tries to close a connection which is already closed instead of just doing nothing? During dev, sure, do whatever, but replacing ifs with asserts just makes prod more brittle.
@@az8560asserts are used for testing that your invariants hold. Invariants are things that MUST always be true. If your invariants do not hold true, your program has entered invalid state. From there lots of things can happen. Some of them potentially dangerous.
Using asserts helps you be sure your invariants are uphold. And if your program enters an invalid state in pro (which should never happen) it’s better to crash than to potentially cause more trouble. Anyway, if you’re program is in an invalid state, it’s gonna either crash or produce bad results which you wanna avoid.
And if you think assertions in a long running service are bad, that’s because your thinking they will terminate the program. Generally, assertions raise exceptions that will be catch by some piece of code (like your http server library) so that request in gonna fail but not others. In languages like go, assertions will panic, but you can recover from it (for example i think the stdlib http server already does by default).
Even if your program really crashes it’s not a problem because it should be run in some managed way that would relaunch it if it terminates (like rc scripts, systemctl or docker).
@@az8560for example in a software that does some kind of (monetary) transactions, if your discover at runtime your state is invalid, you return a 500 server error. Does not make sense to proceed.
You can add custom error handling code to return a nice message or something but most of the time it’s not important and it’s easier to just say THIS MUST HOLD TRUE IN ANY POSSIBLE UNIVERSE and if it’s not the case, which should never be, just fail.
Cue the John Carmack clip where he says the older he gets the more he uses and appreciates asserts everywhere.
The way that mr. prime teaches is honestly amazing. I must aim for this way of understanding what I'm learning. Since my algorithm design class is killing me.
I added an assertion the other day, and it correctly identified a missed assumption, but the assertion was in the middle of some code which had mutated parts of an entity, and then we ended up with data which was half-saved and incorrect after the assert. Boss said "i don't want to see another assert ever again". On the one hand, we are now not able to assert that some behavior is correct, but on the other hand if you have a hot path used by millions of customers, you don't want that to fail. Something you can do instead is to create an assert function which throws in development, and only logs a warning in production, if you are afraid of creating downtime/extra errors.
Bosses certainly know how programming must be done, right? Everyone must be happy to be around your boss...
Short, very informative, I agree and have been following the same pattern. Thanks @ThePrimeTime for sharing.
I really prefer Praying Mantis style programming, but I'm glad I heard you out...
what is Praying mantis style? A part of your code just pretends to be a branch, but when a bug appears, it reveals its predatory nature and devours it? (I actually think you are probably just joking that you are praying for you code to not crash... but I can't stop the thought)
@@az8560 Ooo, so when we let nanomachines into our codebase to heal our bugs, its called Praying Mantis?
@@az8560 Isn't this just a joke about these bugs being natural enemies?
@@NotGarbageLoops you mean praying mantises being enemies with tiger beetles? never heard of them being natural enemies. why would they be, they hunt different prey in different places?
I like this. It feels like it's a great way to build in a manner that's robust and efficient.
Bro's gonna end up in haskell. It's inevitable. It's god's will.
My life changed after I started being pragmatic about adding assertions in runtime code.
I’d love to see more content on how you construct your classes/functions to be “unit testable” without needed anything like mocks. I’m so used to the DI/mocking style of programming that I can’t even understand any other way to construct the code so that you don’t need mocks to test.
I need a full video rambling of this concept. This video is just enough to peek my intrest but i didn't GET it
What you are describing here is called Offensive Programming. The "if conn != nil { ... }" approach is Defensive Programming. Totally agree that Offensive is generally better than Defensive. Great example!
Can also be offensive, if you write if conn != nil { panic() }
The difference is that the defensive one handles error, while the offensive one throws error.
This is called "Design by contracts". Also lot of cases with mathematical software proofing this technique is used.
Maybe try Eiffel or Altelier-B some day? Very interesting languages, Altelier-B being more enlightment and Eiffel being more approachable.
This is how I always saw my coding projects yet inexperience kind of forced my hand to not be so thorough and try to make things work and move on. If it doesnt work thoroughly and predictably it needs to be handled to the point of minimal error. I think we need more of that in game dev in general since it would help with understanding and fixing issues a lot faster and more concise. I mean what you were saying about how you could just put an if statement that would just verify there is a connection that needs to be disconnected but not being able to call disconnect in the first place made prevent other issues that werent foreseen since the proper validation was authenticated. Good stuff Prime cant wait for you and Thor to make a game together lol.
I program the same way. Didn't have a name for the style. Now, you've have made TigerBeetle a more widely known term. I like this!
You're gonna love branded types. Compiler literally doesn't allow you to pass in a not connected client. But obviously those two things go hand-in-hand
Whats the difference between type checking and type-"checking"?
@@statelessdevdo you mean that in irl, the categories have more complex connections than can be described with types?
@@sohn7767 Everything is more than one type. A mug of coffee can be of the type of cup, or liquid container, or looking it from a geometrical mathematical point of view, it can be a type of donut. Or a type of beverage, or type of item on a restaurant menu.
What would be the type or parent type of this mug of coffee that works in all contexts across our codebase? Or do we have multiple types for the same thing (ouch)?
Maybe all the possible complex connections could be described by types, but it adds a lot of rigidity to the code. I think a better approach is to let data just be data, facts that are either there or not.
By decoupling data and functions, it makes it easy to add more functions without having to redefine and recombine a collection of attributes or facts into a single type that makes sense in all contexts.
Types certainly have their value in certain contexts, like at the boundaries of an application. Organization (like pigeonholing everything into types) is just a tool which in itself doesn't reduce complexity.
@@statelessdev very thought provoking take. Thanks for sharing
@statelessdev you argue that a mug can be all sorts of different types -- which is absolutely true. But a mug looked at as a geometrical object is very different from a mug looked at as a container, and we need different information about it depending on how we're examining it. So yes, there absolutely should be different types depending on how we're looking at it.
Let's talk about a real world example. Having multiple types for the same thing is not as much of a headache as you portray it to be. With refinement types, you can add a predicate that "d.conn != null" (or more realistically, "is_connected(d)". Then the type system can figure out when that's known to be true and when it needs to be checked before calling the function. Or we can use things like GADTs to index our client by a type level value indicating whether it's connected. We call connect, and at the end it says the client is now the connected type (FStar and Idris do this with mutable references, though most GADTs are used with functional programing). And I'm mostly familiar with how functional languages do this; there are other powerful imperative languages like Dafny that offer similar techniques.
I love this because it's a great combination of analytical and empirical and test-driven approaches, but it's not TDD, which I love in theory but not so much in practice.
I was wondering why I couldn't find your short on this topic! Implementing this style in a F# work project.
I like this a lot but like others I'm uncomfortable panicking on production for an insignificant state. Instead, you can both assert AND check the connection != nil, and modify the assert function to panic in development and log a specialized error in production. That error log, ideally, should be mostly empty so any errors that show up will be noticeable.
Although the more I consider it, I wonder if logging is best for both production and dev/simulation, to capture multiple possible states? Again, the log file should be empty, so any entry that shows up is important.
You do whatever floats your boat. Its your program afterall. And unless you know how to run vast number of simulation and fuzz testing, you will never be confident in what your are building for prod.
Devs who has really thought through EVERY SINGLE SCENARIO, and are confident nothing can break their app. In those cases, the asserts will be great because it will force you to take action when the application does really breakdown in one wild scenario which the dev couldnt conjure himself. But you have to be confident that those will be rare.
Been long loving the idea! Assert your invariants, and try to use type system to make invalid states impossible to represent. It's not trivial, but I feel most of us don't take full benefit of type system. It goes way beyond simple "is this an int or string"
As a person obsessed with how things can go wrong, this satisfy me
This feels like unit testing but easier to incorporate in the normal coding flow.
Unit testing tests functions. Assert tests the state of the running program. They are not the same and are both necessary.
@@SaHaRaSquad state, and validating inputs for correct API usage(which im not sure if it is good to do so in asserts)
Nice, I've been following this style since I watched Handmade hero series, Casey too does this throughout all the code base in that series
I love his stache.
Man I’ve either missed the streams or you never actually deep dive this build but man seeing a DST style build for a project as you build it would be sick
Pre conditions, post conditions, invariants. Design by contract? Reminds me of Eiffel
Literally those were the first programming concepts I learned at my college while studying CS. Te amo Javier Marenco.
This video is pure gold, thank you
Very good approach, Varnish is also built on asserts. The code should always be executing in the correct context.
Hoare logic is from the 1960s. Dijkstra wrote a whole book on using this style in the 1970s. Meyer developed the Design by Contract style in the 1980s. There's nothing that computer programmers love more than reinventing the wheel.
You should make a video going over your assert module
With my Script2 API I use C++ macros for my asserts that do different things in different models. D_ macros run at debug-time. R_ run at run-time. A_ run at debug and run-time. C_ is configurable to run at run-time but always run at debug-time. Script2 uses the Chinese Room Abstract Stack (Crabs) machine, so it's easy to save the exact call to bugged functions by the nature of the stack. We also don't have any problems with memory safety with our all contiguous design.
As a Test Automation Developer, this makes me happy
Bro i love this content thank you sir
I think the most important thing is the TYPE of assertions. You don't want too many low level assertions. Things subject to change or things that could possibly change. It's the same thing with unit tests. Too many (and dumb ones) make it unnecessarily slow to do anything in the future.
Tigerstyle simulations?
Hell yeah
Am pretty sure that this is supposed to be for the Vimeagen channel but thanks for the video Prime!
doing what your said in my c++ game engine for years and thats how should programs be programmed
It's just normal. You've become normal.
I'm so happy I'm not the only one who try to prevent very rare errors. I remember many years ago, I've decided to create a 'Very Crazy Error' message. All the values in the subroutine were dealt as positive integers. 'Very Crazy Error' was printed on stderr only if the integer was negative, which was impossible. Well almost. Many years after, somebody created a sub that called my former sub, guess what, every thing seems to work until... The program crashed with a 'Very Crazy Error' as the only error msg available. I told them to grep -n 'Crazy' in the source code. They fixed the error in three minutes, after a little brainstorming. I was proud of myself. More than 10,000 lines of code in that module... (Today I would describe the error better.)
This conception looks like Bertrand Meyer's design by contract
This is a good style!
Slither is built like this, it worked fine since it's a locally run program. For a production app, I feel like it makes more sense to log out these errors instead of basically DOSing your live app. Although, I could see why force crashing the app would help a fuzzer quickly assess that something went wrong and state needs to be captured. Fuzzing doesn't catch all bugs, though. Maybe a flag to toggle between asserts and logging makes sense?
I love how us programmers use the English vocab
This is similar to adding precondition clauses in annotations above a function in Java, which I had to do for a course assignment. But more sane
seeing the actual simulation mutation logic would be very useful along with this
Yea, ive been loving asserts recently. My main annoyance with them is how pretty much all standard library assert implementations will be stripped from optimized builds.
I get that it was probably a carryover from the early days of programming when it was costly to leave code in that you expected to never hit, but nowadays, how do you expect to catch any bugs when all the checks are gone in the production build? The users are guaranteed to be testing your software way more than you are.
This sort of programming is so painful, constant error checking ... reminds me of my C coding days 30 years ago ... have we really gone nowhere in that time?
@ThePrimeTime, which golang package do you use for assertion?
This was quite interesting, I've actually found myself on a somewhat similar thought path for the past few weeks.
At my current job I work on a large website that has been built and maintained by multiple past and present employees so I decided to work on a mobile app outside of work to keep learning and trying a different type of coding experience. I'm very used to wrapping everything in try-catches and validating within each method that the call was valid that I started building in that style within my own app. But then it occurred to me, why would I ever call these methods in the first place without the necessary prerequisites? And with state, why would I have nullable attributes on my types when I know that at certain point these values are required everywhere? So that got me thinking about instead trying a different approach, as I'm the only one working on my app it'll obviously be easier to be strict with usages and attribute setting on my types.
I'll certainly be trying asserts throughout my project, that makes more sense to me and I've never really liked having to rely on mocks anyway.
I've been trying this. Using asserts non-sparingly, and it's a bliss
Guess I am on the right track. I just kinda think this way. I assume the same rules as math, i just adjust the sentiment of the acronym. Things just have to run in a certain order, which seems plainly obvious to me. Testing my own flaws is a given. It's just the way I am wired. I love to build things... just to see how i can break them.
Speaking with your hands... this is new :))), it is like I see Joran Dirk Greef speak :)))
So just so I understand, you're saying that it's better to throw errors earlier in the program's runtime than later on? Makes a lot of sence.
So like, if some program typically runs in such a way that functions "A,B,C" typically happen first before, "C,D,E" (assuming they share data). That debugging and error checking is easier when you add asserts to functions "A, B, and C".
I think you'll love the likes of erlang or Elixer, as their programming model is essentially "let it fail, we can recover"
Being doing the same and it's amazing. One thing that I haven't seen talk a lot though is how to do the simulation and fuzziness. Would be cool if you could share that.
The title made me think of an interesting video. Could you do a video where you review some code you wrote when you were a junior and do a roast on it.
this is really great advice.
but just to be clear, this does NOT mean "do not unit test".
if you only rely on this practice you expose yourself to "burn-in" periods of fuzz testing, and at some point you need to decide if you waited long enough to consider the system stable.
that's not enough.
the system is not right if it doesn't crash for a month. but having unit tested all possible edge cases from day one is unreasonable for most problems.
do both.
inform unit tests of crashes happened during fuzz testing.
Now, I feel like I am living in a simulation. Cuz the exact thing Prime said.
Now imagine a world where more engineers did this. The enshittification of software would slow to a crawl real quick.
You should check out clojure specs and property based testing. pbt started with erlang tho.
Pretty nice for catching Heisenbugs ahead of time, but hard to prove to management that it's worth it 😉Wondering how it would hold up against a "current" (recently discovered) Heisenbug.
Thank you!
Good advice, miss the old closer though…
Great advice!
Prime should check out the typestate pattern. While assertions are nice, compiler proofs are nicer.
I feel like this was supposed to be uploaded to TheVimeagen
at 1:25 you can just indent inside the brackets with >i} instead of entering Visual mode
Can you do a what is simulation and how to do it properly type vid? Like with a minimal example? It's hard to do it right and it would be great to hear your thoughts on it. Thanks for the video! :)
2:22 great rhyme to live by
it's so amazing, pls more info on how to write simulatable code like that
How is this different to
if err != nil {
return err
}
The error will be returned upwards and will be handled by whatever monitoring system you have and you will be alerted. If you want to add some extra information you just create a custom error type
Wow, I should try same style
a.k.a "let it crash" from Elixir/Erlang
Assertions are fantastic
I've been playing with similar ideas at work and it does seem to work well. The only issue I've had is in dynamically typed languages it significantly ramps the cost of change. I love it in c++ but found it miserable in python.
I've seen this called "make invalid states impossible to express"
Maybe you like to check out Eiffel ?
This is similar to modelling invalid states to never occur through a type system found in ml style languages
Tried using this approach a bit in Java in my job and also felt it was a nice approach, but sonar complained when I tried to merge my code. I might have another look to see if I can use assert
I mean, you could also just throw an Exception
You should not. Assert is for test code, not for production code. I work with saas environments that won't even let you deploy with asserts because it is a flawed programming style.
Also asserts overhead is not worth it.
What he's doing here is having his code also be his test code. That's not good
@@mattymattffs "that's not good" tell that to all of the heavy users of Erlang/Elixir. their entire programming style is letting things fail in prod. and it ends up making some of the most resilient systems that we currently have. (phones, discord, whatsapp)
@@mattymattffs asserts are supposed to be removed from Release builds by the compiler.
TIGERSTYLE!
i'm excited to try it out
btw it was recommended in effective java book years ago:) (also java was memory safe a long before rust created and without async issues lol)
The Golang standard would be to return an error. Asserting in a "test assertion style" throughout your code is not good practice in Golang. Panics are nearly always a code smell, not a guardrail. Why not write an assertion library that returns an error? `if err := assert.Thing(); err != nil { return err }` You could even have an array of assertions and check them all with one line. `a := assert.Many(assert.Thing) ... a.Add(assert.AnotherThing) ... if err := a.Assert(); err != nil { return err }`
ELM opaque types all over again.
asserts everywhere sounds like javascript programming, some do a lot of asserts as well... :D looks like prime is rediscovering how javascript programming works :D
Bro is starting to reinvent the Actor model.
Maybe if he wasn't allergic to reading whitepapers.
I wonder if its possible to develop an interpreter that writes automated tests from the assert statements in your code
I'm piggybacking on Prime to becoming a better programmer
Assertions sound cool in development, but I don’t want to crash a live production server in production due to some unforeseen error.
In most languages (PHP for example), the default setup disables them in production for performance so you can leave the assert() functions in the code and the production server completely ignores them. For years I didn't know that so avoided them as the thought of commenting them out/in every time I made changes was horrible.
Saw "assert" in the thumbnail and thought it was gonna be TDD 😅
What if you disconnect after you check if you are connected?
asserts in production code is cancer
How do i use the assert package in my non test file ?