KotlinConf 2019: Error Handling Strategies for Kotlin Programs by Nat Pryce & Duncan McGregor

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] so hello this is net price hello Duncan McGregor hello we are server-side cotton developers for London we have as many years experience as our failing eyesight and gray hairs would lead you to believe and one thing over the years we've discovered in many languages in many systems they're all subject fania different languages have different ways of dealing with those failures but Kotlin is shall we say conflicted so we thought we'd give a talk about the error handling strategies we've used and how to choose between them great error one can't change slides yeah all right so if failure is not an option what is it my dictionary says lack of success which just makes another question but it also helpfully says the neglect or omission have expected a required action which is more helpful so let's just say failure is the inability of the system to do the job that we wanted to do it went wrong yeah so what that means is for example helpfully handling invalid user input we're not counting as a failure that's something our system should just do as part of its normal operation so how to program to go wrong and when we say program we mean functions methods procedures any any codes that we're invoking well often we don't give them the correct input they need to do their job sometimes they need to talk to other systems and there's other systems failing in some way or communications with their systems fail and I have heard of programmers making errors apparently they repulsed the end of arrays tried to get the first item Vint lists and and sometimes the environment that our systems are running in fail we might run out of memory and not get a load of class those sort of things and there are there are other ways of failing but these cover most eventualities and the way we will look at things for this talk so it turns out that handling errors is difficult to do and itself is very error-prone and software engineering researchers have been people research and study software engineers have found that it's very common for error codes or error report signals to be lost as they travel through layers of our software stacks and that when systems go wrong in catastrophic ways a large majority of the time they went wrong trying to handle recoverable errors and the logic that was triggered to try and handle those errors contains bugs that could quite easily be caught by unit tests error handling is difficult to get right in the architectural level and even difficult to get right in the small in the logic why is it hard well we often don't know how anything operation could fail so we don't take into account as I think a fail knowledge of how to handle an error could be a long way away from where the errors detected errors can leave our systems in states from which we can't recover an error handling tends to be hard to isolate from the code from the surrounding code and therefore it's hard to test and therefore we don't test it yeah so something's hard work if it's error prone that's the kind of thing that I think computers were invented for I want the computer to do the drudge work so that I can focus on the bits of the job that I enjoy more so Kotlin inherited a lot of concepts from the JVM from Java and Java introduced checked exceptions when it was first defined as a method of helping programmers deal with the drudge work of handling errors and known that their error handling is covering all the cases so earlier languages had introduced exceptions before then but Java introduced him at the type level a signature of a method would include the checked exceptions that it could possibly throw and code that would call that function method would have to either propagate those exceptions or handle them in some way to pass the type checker so so that was great I'm I think both are among the rare breed of Java programmers that like checked exceptions five or six versus yes and and you know when I first learned Java I remember reading a document that explained the exception system that sort of I've summarized on this slide here where I said okay checked exceptions are for things that happen that can go wrong in the environment of the program and your program has to handle these kind of errors therefore they'll be checked by the type checker and there are other categories of errors that can happen at any time that is not worth type checking there are there errors that a programmer makes that can be detected by the runtime those are reported as a runtime exception you know there's no point in type checking those because well if you knew that you would make them you wouldn't actually make that error in the first place and then there are errors which are reported as a error which is where the actual runtime itself can no longer support the semantics of the language it can it's failed to load a class that your program has to execute maybe it's run out of memory and again there's really nothing your program can do about that the entire runtime is now untrustworthy and thanks to mutable state even a programming error in many cases in certainly an early Java programs in style that they adopted would leave your program where the state of your program was in an unknown State and really at that point all bets are off does anyone actually recognize this categorization of Java exceptions has anyone ever read that document a few people yeah I've never found that document again I don't know where it went I am so that was a good idea and we loved it but the exception class hierarchy was awkward and after Java was released they couldn't change it runtime exception became a subclass of exception which nobody really ever understood and from the start Java exceptions weren't used consistently in the standard library so URL constructors through checked exceptions but pausing integers from strings didn't assertions became an error not a runtime exception and it got worse over time especially with lots and lots of open source libraries all of which had different conventions and in the end popular frameworks took kpi's through checked exceptions wrapped them in to unchecked runtimes exceptions making absolutely everything worse and in the end Java just gave up I think that yeah yeah it's pretty common now just two for everything as one-time exception and of course higher-order functions also made that more complicated yes so at the point of Java lenders the idea that if I'm calling a function that could throw an exception I can also throw that type of exception I think the language designers at that point too through the hand up and said it's all too messy nobody actually really cares about checked exceptions anymore and they gave up yeah and now Kotlin so Kotlin i think as a perfectly understandable and usable pragmatic design decision doesn't have checked exceptions turns out that checked exceptions are a java feature they're not a JVM feature and so Kotlin can throw unchecked exceptions even when Java would consider them to be a checked exception however you know at this point we now loft our ability to type check our exception handling code and certainly when I went from Java to Kotla and that was one of the things that sort of surprised and shocked me and we had to come up with other techniques for representing propagating and handling errors and that's really what the rest of this talk is about so what is the best way to handle errors in Kotlin it's important and how do we as calling programmers get back to the consistent characterization and reporting of errors supported by the type checker that we liked so much in Java well some was like so much you know it depends okay not an ideal answer but I'm a consultant so that's one I i give quite often but what we're going to talk about is you know what does it depend on we all write different programs some of us write back in Web Services some of us write browser side code the compositor JavaScript's and what's right Android some of us write embedded systems some of us move between all these things though you know I've worked in also different systems of different sizes different resource constraints and the best way of handling errors will depend on the performance constraints your system the your team's preferences and many other you know wider architecture that it has to fit into and what that depends on will change as your system evolves so what might be a perfectly reasonable decision at the start of a project when the code is small will have to be re-evaluated as your system of awls grows and changes in it and is used in in wider and different contexts and so we'll also talk a bit about that so we want our tooling and the type checker in particular to help us you know with our search system right now and also to help us manage the evolution as as it grows and evolves and we have to change our handling strategies so what can we do we could just throw exceptions right they were in the language already we don't need to get another library we don't have another dependency that we have to manage so there's a lot of benefit of just using exceptions because they're built in they're very easy to throw and in fact possibly too easy to throw what happens is that many different exception types can propagate up to code that has to handle errors and perform logic on error handling logic and then we end up with code like this where we do have to catch certain errors this is an HTTP POST Handler if getting a form there's it's reading a form which might fail it's expecting there to be an ID field in the form in the hex specs we have to parse that into an integer a big integer in this case and it can throw either no such element if there is no form field of that value of that name or a number format if it's exception if it's not a valid integer and both of those should be reported back to the client as a 400 so there doesn't retry an invalid request any other kind of exception should be reported as a 500 so that in the future it could retry the request so that makes the error handling logic you know overly verbose we have to handle each of those cases have some duplicated logic so in in practice what we do is we translate errors as they cross from one bounded context when domain in our application moves the domain of positing forms into another domain may be the domain of handling web requests in a way that simplifies the exception handling logic so we abstract the many different ways a lower-level code can fail into us of an abstraction of that failure to simplify logic at the higher level so here we will catch a number format exception and for a bad request we will handle the case that a form field is not there throw bad request and now our HTTP layer only has to worry about bad requests exceptions and doesn't really care about the exact reason that they were caused in the lower level code and that's really one of the things we want to get across in the talk is you know how do we hide and abstract different kinds of errors away and translate them for the convenience of error handling logic however without support the type checker how do we know whether we've handled and translated all of those exceptions this is a slide I snuck in at the last minute Duncan there was a bug in this code and can you spot it it's a trick question a trick question because of course exceptions on top checked they're not in a signature there's nothing in there that tells you that I've changed my code here to use JSON or a form and now that Jason library's throwing different exceptions and there's nothing in my past requests that has to handle the requests those exceptions from the JSON library and so they bubble up to the HTTP layer where eventually they propagate all the way up to my my server implementation and they get returned to the clients of 500 and it will then retry an invalid request so by not checking the propagation of errors we have now created an architectural problem which might end up causing like requests storms in failure scenarios and things like that so the the logic in the small can have wider effects on the wider system across our system that's why exceptions checked exceptions were so cool they were call but they did now grieve move on what we do do is we first test our code up to make sure that they only throw the exceptions that we expect them to throw so we can't type check it but we can test it and first testing is one of these techniques is unreasonably effective at uncovering these kind of errors we just take some of the good data the good inputs that we've already that we already have in our positive test cases we mutate it I'm using a library here called snorcher I wrote so available on github but you know there are other libraries that do the same thing we mutate that input we throw thousands of mutated copies of that input into our parser in this case and it it might or might not fail but we want to make sure that if it does fail it only fails by throwing exception that we wanted to throw and it never throws an exception that we don't want it to throw so exceptions are fine you know there's nothing wrong with exceptions within a context in which their disadvantages are not going to cause you problems so if it's if there's a small program if there's a single point in your code where all exceptions are handled it and and have exactly the same behavior where you don't really care about the type of the error you don't have different logic depending on the type of the error then exceptions are a perfectly good choice if I'm writing a command-line application it just has to fail and return an in various status code if if I'm writing a web handler it can just return a 500 maybe my you know I can write a dialog box and it to publication and so on okay but we do have to be aware that when this context changes we have to maybe choose a different error handling strategy so for example we were we have some client api's that call sort of services that are provided by other organizations and other parts of our organization we have our local abstractions of those if we can't communicate with one of these services it'll throw an exception and arc overturns r500 great that's fine but then we started using this client in a batch job and what we didn't want was an exception to propagate out of the batch job which was handling thousands of entities rather than individual entities and roll back the entire transaction we wanted to be able to have logic that said right we will we will fail individual entities but not the entire transaction the entire batch and we wanted that type checked so we just at that point we chose no longer to use exceptions but to expose the errors in a way so we will look at the rest of this talk that could be type checked context changed and so we changed our design to help us use a type checker to make sure that our the rest of our code was then behaving the way we wanted and we were aware of the error cases and the compiler was helping us stay aware of it even with even with other techniques though you should probably be using exception for the JVM errors out of memory can't find a class and probably program errors as well so invalid argument in each of the state where we haven't checked things properly they are pretty much unrecoverable because we weren't expecting them and we need to know as much as possible about where they happened because we weren't expecting them and so the stack trace is there in particular really useful yeah and and you know try and avoid swallowing exceptions or throwables let's say that there are reporting these you know unrecoverable system or programmer errors so don't catch throwable in your code because you will now be losing this information and maybe continuing with the with the entire system in a very broken state and it's just just catch exceptional the exceptions that you know that you have to catch because you've fuzz tested I so before we go on and look at some of the what we can use in place of exceptions let's stand back and think why we doing this at all the problem with errors is they contaminate if my request processing needs to pass data and that can fail with an error then the request either has to be able to cope without the data will fail itself and that happens all up and down our systems in and out of our systems and if enough code of the leaves at the tree can fail then every path through our codes is subject failure and we have to choose between strategies and we have to decide how to represent those failures and and how to handle them and in practice this happens pretty quickly in most systems so practically every function invocation is subject to failure which is why Java programmers pretty soon punted and just made everything throw unchecked exception so how about we write functions that can't fail how do we do that Duncan well so our first category of errors was invalid inputs so here fetch fetch will have to fail if that string can't be passed as your I and so every function that called it with a string will have to do with the failure and if a function can't cope with all possible values as arguments then it has to fail that's often with the legal argument exception and we call these partial functions they are only valid over a particular range of inputs and the opposite is a total function total functions can return a result for all combinations of arguments supplied and when we call them we don't have to consider exceptions well with the exception of i/o is it guess yeah so in here we've just changed to pass a URI rather than rather than the string now we're really quite used to doing that and if the source of the URI is is a string from outside or somewhere else and it almost always is and it might not seem like we've bought much it's going to fail somewhere but if we make it fail at the point where we read the string rather than the point where we need the URI then all the intervening code doesn't have to consider the consequences with fact failure so these partial functions pollute our code and they hide everywhere so the fetcher here is doing two things it's reading reading from config and it's reading from config in the effectively in request processing at some point and because the base URI conflict parameter may or may not exist and it may or may not actually be a valid URI that can fail at runtime and in the middle of in the middle of normal code go on and since solution here is once again to pull the error-prone code to the outer layers of your system in this case at the point where we construct the fetcher and in that case fetch now becomes a total function again and all the code that calls it doesn't have to worry about errors so what back to other ways of errors yes so yes we still have errors how do we represent errors in a way that can work with a type system and allow us to type checker check them that's our challenge now one option is we just use nulls to represent errors and the advantage of null is it's in the language we don't need another library the type checker will tell us when we're not you we were not handling the null case and there is nice language support for working with nulls and the disadvantage is you've only got a valid value or not a valid value and there's no way of including any context in contextual information any explanatory information to allow the system to either automatically recover from the error or to report something useful to a human operator so you know is useful for for handling error cases in the very small scale pieces of our code passing in a string to an int or to a URI makes perfect sense to return a null because the context of that error is is very small and it's very easy just to look at the string that we passed in to see what's wrong with it and maybe if we have passing a large JSON document or something that wouldn't be enough we wouldn't be able to find the error inside that Jason document but for small strings that's perfectly okay one of the reasons this is so much nicer in as a technique in cotton in than Java is because Java there was no way of expressing at least in the early days that this function could return null and so and callers weren't forced to handle it but of course in cotton we have nullable types yes so the fact that you can pass null into something has to express in the type system and is therefore that that function is a total function while in Java the fact that any type could also be null apart from primitives of course means that every function in your java program is a partial function and this is a common convention in the standard library so we can follow that convention you know the naming convention is to say you know all null at the end of a function that that could fail and will report failure by returning null it's a little bit bit clumsy but it is very explicit and so that's that's nice and so yeah we often follow this maybe if it's parsing a string I might call the function pass but that's that's a convention within our team's code base and something that we all agree on and this sort of wider convention is to say to something or no and thanks to the null coalescing operator and the Elvis operator you know handling these error cases is you know quite quite straightforward here we are like looking for something that's missing and then we're trying to pause it and in any case the null you know is an error and so we can easily handle the error just by using the others operator to say if there's any error one I'm pausing this thing returned bad requests if there's anything a kind of error when I'm pausing the other thing just returned a bad request so that's quite you know quite straightforward to do and it's type checked and and it plays nicely with with writing your own extension methods because send the null coalescing operator like plays nicely it has a disadvantage that we're using the same concepts at the same construct to represent both the error case and what could be a perfectly valid absence of a value and as soon as you have to distinguish between something that's like logic that you do when something is missing and logic that you do when there's an error then the the language support is no longer as as convenient as possible so in this case let's look at just the first example here we we want to be able to provide a default value for the count parameter and it has to be an int if we pause the int and that's that can't be paused that null case is an error but if the count field is not in the form at all that's perfectly valid and we want to default it to a hundred and you can see the logic now is looking quite a bit more complicated and we still have library functions that are going to throw exceptions to report errors and not return null to us and so we're gonna have to convert those exceptions to know in order to follow this convention and so we try and do that sort of close to as close as possible to the function that we'll throw and and also you know first test it to make sure that we are handling all the exceptions we expect as and return them into null and not allowing anything to propagate through that we don't expect so using those is fine when the cause of an error is obvious from its context and we're not we don't have to represent optionality and errors in the same code and in the same bit of logic so for example pausing simple values as we've seen looking up data that may not be present okay but and again we have to be aware when that context is changing and and then maybe use in a just a different technique as that context changes and we use first testing to ensure that we are doing the conversion from exceptions into nulls and that we're using library functions that we're not entirely sure their behavior in a way that is appropriate it's we're saying that go back one thank you that the advantage of now also mainly extent using an option but that does lose all the nice language support that Colleen gives us and hence the title of this talk failure is not an option so we saw before how narrowing the types of function parameters we could make partial functions total so that we don't have to worry about errors for more of our code base we still need to convert more output to acceptable input at some stage but the further to the outer edges that system we can do that the less code has to cope with failure now we can extend that to other types of errors not just invalid data in particular io is very susceptible to errors but we often read too and write from read from and write to external systems in the bowels of our code in Java we checked exceptions that meant that either most of our code could throw i/o exception or we would just lie and pretend the database called can fail and smuggle their errors in unchecked exceptions when they did so pull the IO up to the outer layers read values into memory early validate them early transform the total functions that return values to be written back at the outer layer in this case we have a function that is taking some data from URI and writing it into a file it's reading nicely at the top level pulling all the data out and then it's processing it but you can see that process is taking file as a destination we know that it's got to open the file it's got a right to it so that means that process is a function that's really should declare its throwing i/o section we have to guess that it's throwing i/o exception another way looking at this is that functions returning unit are suspect we know that they must be doing something they must have side-effects and that's often an i/o or other side effects are often subject to failure so we can transform this code into the code on the bottom code which basically says let's pass values down our system and pass results back as values and if we do that then the i/o and the things in the subject of failure can move to the out of us outside of our system more of our system becomes total moralist system becomes not subject to failure and we don't in all those cases we don't have to choose between exceptions or nulls or other techniques that we might find out of that okay so another option that we could choose is to use what functional programs would call an algebraic data type in court limit call a sealed class hierarchy to represent the case that that you might have a successfully computed value or you might have a failure that was caused by some reason and here's an example this is from some example code I wrote called result okay that turned out to be useful enough for people to start using in their production code but it is only three lines of code so you know fair enough so here's an example where okay a result can either be a successfully computed value of type T or an error that happened for some reason of type II and and there are those two cases are handled as subclasses of result which is just simple data classes of success and failure and of course and this is just one example of this there are you know other examples you know in other libraries that you can you can obtain and there is a result type in the standard library but it isn't an algebraic data type and when you try and use it IntelliJ complains so probably better ignore it it's really just seems to be used for co-routines at the moment yes now the advantage of using a sealed class hierarchy like this is that you're forced to handle the fact that something might fail in order to get hold of the successfully computed value the exhaustive Miss Cheng in the compiler will not allow you to consider the success case without also considering the failure case and doing something about it so you know which is great that's what we want but now our just pushes the problem a little bit further that and creates a new one because what we don't want is to have that ugly code around every single function call that we call in our system or at least any fallible function calls we call in our system and how do we represent the failure reasons themselves to make sure that we can type check that as well we have just moved the ugly code mind you Wow okay tis ugly in some way so we've add or define useful functions extension functions on that seal class hierarchy that allow us to focus on the happy path and and let the let the functions collect in report and shortcut error handling when an error happens so flatmap will take a success value and call a function that might return it and it returns a results I might fail resulting in you know the the final result of that function but if the original value being that it was called on was a failure case already it will just result in the failure case so we can focus on straight line processing of our logic and let the let the the convenience operations handle the sort of propagation of errors for us and till the point that we want to do something explicitly about those errors in which case we have some convenience operators that will allow us to get hold of the error reason and do something about it so in this case we read some Jason we perform a command that has some kind of outcome and at the point we're at the map we have either a failure or a successfully computed outcome we want to send the outcome back to the client so we can map it into an HTTP response which is just another data structure it's not actually written yet and we if there's a failure we also want to report that to the client so we have another convenience operator map failure which does the equivalent for the failure branch of that that sealed class hierarchy and that will map the error reason to to an HTTP response giving us a result of either an HTTP response for the success case or an HTTP response for the failure case which means that both branches are holding the same value and there's another convenience operate of it then just returns us back the HTTP response from either of those things they're both the same type and we've now got back into a total function and we don't have to worry about results anymore and therefore we can return a response back from this function if you're used to handling except handling errors with exceptions a team may bulk at this for a while hmm you have to back into code like this gradually so I think it's quite a sophisticated technique but one that's worth yeah and if you've got scholar programmers they would like hold on that last map and Matt failure and get that's just a fold in Scala let's collapse it into a fold so you know I'm not sophisticated as they are so I'm like I can't read that let's break out those different cases so I can see what my logic is so you know now the reason that this is so easy to read and like it looks so linear is of course that each each pipeline step is just passing along a value to the next pipeline step and and they don't need to use steps from values from earlier in the pipeline here they generate an HTTP response we actually want to use the request that and some other information from earlier on in the pipeline and now we've got this in order to do that we need to nest to the blocks and ran and the flat maps and they're suddenly our code is not looking so pretty I mean that nice linear pipeline of computation is is represented as deeply nested function calls and Kotlin doesn't have language support for monadic comprehension and functional programs would call this stuff and and so we're just like resorting to do it with with functions in it and it can get quite hard to read yeah I tried to not mention the monad word I failed arrow has a very clever technique using co-routines for flattening out these nested flat maps into syntax that is you know inspired by scholar and Haskell very nice we don't actually use this in production because it's still an oppressed is they still haven't released the one point naught version and the api's are changing between releases and and so we've you know we and and the performance isn't optimized yet so so we're not using this at the moment instead we use the fact that Cotton's inline functions allow you to return from a lambda out from from the from the enclosing function named function so we define an operator on failure that is given the failure and we run a inline block that means that inside that block we can return the failure straight from the function and fuss in the pipeline returning back the successfully computed value just unadorned by the result wrapper so this flattens out our logic into something that looks much more familiar to you know procedural programmers and you know but it does come at the it comes with a drawback of yeah there's quite quite a bit of boilerplate code to handle these particular failures on the other hand the control flow is never very explicit and that makes me happy when I'm coming to code that I don't know which is often code that I wrote a few weeks ago so yeah it's a trade-off right you know if you can handle the the sort of API churn of arrow as it's stabilizing maybe that's a good choice for you this is the choice that we've gone down I think this is the sweet spot for me certainly this is this place the strengths of Kotlin as a style and allows you to know when things are going to fail in it now that still means that you know we still have to do something whether there are reasons how do we represent them and you know you could just represent them as an exception I think the result type is actually in the co-routines library does that and and then you've got a big hierarchy of exceptions and some of those exceptions are viewing returned in the error branch of your of your seal class hierarchy and and that that's still quite fast exceptions are slow when you throw them because the the JVM builds up a representation of the stack and in the stack trace of the exception and that can be quite slow which is why people say don't use exceptions for flow control but if you just create an exception it's just an object on the heap and so you can use them the the risks that we found there is that eventually almost everything with the result of tea or exception because because you there's a single unified base class for exceptions and it's easier just to return exception and now you've lost the ability to check the exhaust stiffness of handling all the error cases because you have to consider all the possible exceptions and then when you know you're back where you were when you were throwing exceptions and so we prefer to create a hierarchy of sealed hierarchy of data classes to represent error cases and and then you know we made the mistake of having a single type root type of all our error reasons and ended up back in a situation where we could no longer type check source of all the possible cases because we had like a massive hierarchy these things so we ended up back in the same problem don't do that if they're something you can learn from that what I would do now is like define small hierarchies of error cases for different bounded contexts in my application and then explicitly translate those error cases from from one type to another type as I crossed the boundary between one context and another context so that I knew that in each each citric place in the code I I was exhaustively handling every single possible cause of error often it's not just a veneer immersion yeah yeah in many cases in enum is fine sometimes you want to associate more data with it do we care about the stack traces the you know the what you can have stack traces on an exception you can't on a data class unless you jump through some hoops it turned out you know we what we found is no actually for anything we're explicitly reporting errors if you don't care where they happened the the data model has enough context about the applications processing and context to for us to understand why their error happened exactly where it happened in the code not really important if it's an unexpected exception if it's a programming error then we really do care because we want to dig down and find out why that happened in which case an exception thrown by the runtime is fine and we just let that bubble up to our top-level exception handler which will report it into our interest six minutes to lunch these people are thinking they're not gonna eat right so results a result soap is fine when your team are used to functional programming style you don't need stack traces you do want to type check your errors and and you do want to represent a little bit more context about what happened when you're here when an error happened so propagating exceptional cases up to web pages you know when you actually want to do different logic based on exactly what happened so maybe there is some things are going to return in a 402 clients versus a 500 looking up data that may not be present we can actually distinguish now between an error or a successfully computed but absent value which is perfect which is a valid State and again be aware of that context changing and just like we do with nulls and Jeff I would do with exceptions you know we convert exceptions to failures as close as possible to the methods of throw them and we first test our code that can fail to make sure that we're not getting any unexpected exceptions thrown out of that code even if you are heading down the route of and results type it's important to remember that errors are a fact of programming life error can be thrown certainly on the JVM from any operation at any time and so we better plan for that so we need to design our system to be robust to errors and we look at techniques like preventing inconsistent persistent state through transactions or units of work so that if something goes wrong either all of our required changes are persisted or none are we want to avoid their form mutating shared state and strive at least to make request processing both leaving everything in a consistent state and actually the call stack is a very good way of doing that if all your medications are scopes to your call stack if an exception is thrown then they can't leak and they'll just be discarded whole process is we may kill whole processes when they detect in since date and restart them from outside and Q's and retries patents for making systems resilient to the failure of subsystems but it's important to differentiate between environmental failures where a retry is a sensible thing and logic failures where if you retry it the code is going to go through the same path and we are going to fail in the same way so first testing chaos monkeys plane table putting into production and logging exceptions will all show us where robustness is lacking in our code and a places to introduce probably in that case a result monad to let us know these as possible so we've talked through some techniques we've talked about the trade-offs that we found between them and now these are this is a sweet spot that we found for our system being web developers of web applications that run in the cloud I think but you know your system is going to be different than our system you know especially for an Android developer or writing embedded code and so you know hopefully those options available to you and the trade-offs will help you find the sweet spot for your system and be aware of the context in which that sweet spot is staying sweet and then when you might have to consider the fact that maybe a new technique is required because your context is changing because your system is growing and and being used in different contexts thank you very much some links for you [Applause]
Info
Channel: JetBrainsTV
Views: 11,687
Rating: 4.8896551 out of 5
Keywords: JetBrains, software development, developer tools, programming, developer, kotlin, kotlinconf, kotlinconf 19, Nat Pryce, Duncan McGregor, java, functional programming
Id: pvYAQNT4o0I
Channel Id: undefined
Length: 43min 29sec (2609 seconds)
Published: Wed Dec 18 2019
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.