Summarized using AI

Workshop: No Static Types? No Problem!

Noel Rappin • November 14, 2024 • Chicago, IL • Workshop

In the RubyConf 2024 workshop titled "No Static Types? No Problem!", speaker Noel Rappin addresses the concerns of new Ruby developers regarding how to write safe code in a dynamic language without relying on static typing. This workshop aims to refactor existing Ruby code to exemplify techniques for achieving data validation and type safety using Ruby’s features.

Key points discussed throughout the session include:
- Dynamic vs. Static Typing: Rappin emphasizes the distinctions between dynamic typing in Ruby and static typing through tools such as Sorbet and RBS, highlighting the flexibility and challenges that come with each.

- Code Examples: The workshop uses a simplified Rails application called "cookie cutter" to demonstrate problems that arise from untyped data passing. For instance, the difference in expected data types between buyer and recipient leads to runtime errors, underscoring the importance of type validation early in the process.

- Early Type Checking: Various strategies for type validation in Ruby, such as null checks, coercion, and the use of 'duck typing', are presented as alternatives to lose runtime errors from data discrepancies.

- Editor Tooling: The speaker discusses the enhancement in developer experience using tools that provide feedback during development, showcasing how type annotations could improve code maintainability.

- Object Patterns: The workshop elaborates on design principles like the Null Object pattern and using Factory methods, detailing how these can simplify handling of different object types and reduce conditional complexity in code.
- Rethinking Object Design: Emphasizing the use of encapsulation, Rappin advocates for structuring code to allow for clearer logic flow while managing data state effectively.

In conclusion, Rappin encourages Ruby developers to utilize existing Ruby features and best practices to achieve safe, maintainable, and flexible code without the need for static typing. Through design patterns, careful object handling, and adopting new tools, developers can navigate potential errors effectively. The workshop provides a robust set of tools and principles aimed at enhancing the maintainability of Ruby codebases, leaving attendees with actionable strategies to improve their daily coding practices.

Workshop: No Static Types? No Problem!
Noel Rappin • November 14, 2024 • Chicago, IL • Workshop

New Ruby developers often wonder how to write safe code in Ruby without
static typing. In this workshop, we’re going to refactor working code to get the
data validation and safety of static typing, but using Ruby’s existing features.
We’ll show how to use coercions, data objects, the null object pattern, among
others, and we’ll use Ruby’s debugger and console to show the power of Ruby’s
flexibility. We’ll even see how improve your editor tooling. Then, we’ll discuss
how static typing can work together with dynamic typing to make the most maintainable
code. You’ll leave with a great set of tools for improving the code you work on
daily.

RubyConf 2024

00:00:15.360 let's get started I couldn't she didn't want me to leave my
00:00:22.640 my uh remote out so I put it in my pocket and then I couldn't remember where I put it which is great
00:00:28.960 um so I want to start by telling a little uh story here uh it starts when I
00:00:35.800 was discussing sorbet with a cooworker as you do if you are me and part of my job is discussing sorbet with co-workers
00:00:42.879 and and offering guidance as to when teams use cor sorbet and this coworker was really pushing on the runtime type
00:00:49.480 checking that they get from sorbet and he presented me a problem that I am
00:00:54.640 going to he presented me a very abstract problem I've converted this a little bit to a more concrete uh problem and let's
00:01:01.519 see if we can do this here
00:01:07.119 uh so imagine I have a very very simple rails
00:01:16.000 application uh it is called cookie cutter this is the application the both the application and the slides are
00:01:21.400 pinned in the slack Channel by the way I'll get to them in a second but if you want to follow along there's really not much to this the application if you want
00:01:28.040 to just open it in GitHub and Trace through through the walk through reading of the code as we go along in GitHub
00:01:33.759 that's great if you want to download the site and look at it in your code editor also great uh whatever works for you
00:01:39.880 this was meant to be low latency low typing High interaction so we're we're going to talk but here's here's the
00:01:47.159 issue I have this cookie cutter app it is it it sends a bunch of things so
00:01:54.079 Bruce and Clark they're connecting a bunch of stuff it creates an order and it prints uh an output which is all
00:02:00.039 great this all works fine and let me show you how it works um uh it is so
00:02:07.399 here's here's where the code sample is it's sln wrapping cookie cutter um it is also as I said pinned in the slack
00:02:13.360 Channel and we're starting in the branch called start uh anyway so there's this web
00:02:20.720 service the controller calls this web service it's called checkout action conveniently and it takes a buyer and
00:02:27.000 recipient which are both uh an internal active record type called person it takes a bunch of line items it takes a
00:02:32.120 bunch of promotions their type is not exactly relevant for this at the moment it calls a manag payment service with
00:02:37.879 the same set of objects and then after the manage payment is called it then creates an order some of you who have
00:02:42.920 done payment services already are already cringing at this but let's we will carry on let's just just assume
00:02:49.360 that this is where we go the manage payment goes off and it calls another weapon web service called handle
00:02:55.040 shipping it actually manages the payment we're hand waving that in a comment because this is not a real app this is a toy problem and uh it calls a third web
00:03:03.760 service that actually prints the label and at this point it it actually invokes the recipient for the first time and
00:03:09.120 this all works well and good in process and then we imagine one day in the future this app gets really wildly
00:03:15.360 popular and we start taking in a thirdparty people can start sending us
00:03:20.440 Json API so we start somebody else comes along and writes a batch processor for
00:03:25.640 this service okay and it looks like this uh it's this is the same process
00:03:31.599 it's calling the same checkout action it's calling uh internal internal uh methods for buyer recipient line items
00:03:38.360 and you can see the buyer and the recipient here uh right here so what's the problem with
00:03:43.799 this what's going to happen when this code runs looking at what buyer is doing and what recipient is doing and the difference between what they're
00:03:53.319 doing uh yeah sorry Mike huh recipient is going to return
00:04:00.760 return yeah huh yeah huh right so recipient is returning an ID or string
00:04:07.319 or something and buyer is returning a person right I
00:04:14.720 huh um so whereas in the past the previous code was expecting the buyer
00:04:21.160 and the recipient to both be people person objects as they come in through the checkout action in this case the buyer is returning a a person object the
00:04:28.199 recipient is just returning the string if I put it this way it looks pretty bald that this is what's happening here but you can imagine this is being part
00:04:34.240 of a complicated system where somebody just misses it right and then what happens uh this doesn't this doesn't
00:04:40.520 work specifically and I can show you here there's a there's a rake
00:04:56.560 task uh and the rake task uh breaks because a string doesn't have
00:05:03.720 an address right and like sort of more to the point the big problem here in
00:05:08.759 this toy contrived problem is that it breaks after the payment has been made
00:05:14.960 so the the payment has been charged the order has not been saved to the database and we have crashed this is bad and so
00:05:22.919 the question was and I like I've obviously this was presented to me as like a skeleton problem I have sketched
00:05:28.120 this out into like an ENT High scenario so we can all feel bad for the poor person who wrote this code um but the
00:05:34.919 pro the question the initial question is like how do you fast fail then without type checking how do you know at the
00:05:42.759 beginning of that process that something at the end of that process is going to use an attribute that you don't have if
00:05:49.319 you don't explicitly use sorbet or some other runtime checking and my immediate
00:05:55.840 response to this was well this is a design problem uh in part because that's my immediate response to anything um but
00:06:03.720 I did come to think of it and I and I started to think seriously about what runtime type checking and static typing
00:06:09.680 checking gives you in a problem like this what it costs you in a problem like this and uh that's what we're going to
00:06:15.520 go over here no static types no problem I am uh null this is you all already
00:06:20.560 introduced myself but here we go again um I hope you're up for this this is as I said um the codes the code uh sample
00:06:27.479 is here at cookie cutter and uh this is a contrived example and that's one of
00:06:33.280 the reasons why we're not going to be doing a lot of typing against it it's like it's it's a Hollywood back lot there's like the fronts of buildings and
00:06:39.880 nothing else if you peek around the side it's just wall um within that but I
00:06:45.080 think we can all understand how this might be part of a larger system and I'm asking you to both sort of use your
00:06:50.199 imaginations about how this would be embedded in a more complicated system and I'm also asking you as we go through
00:06:56.440 the different options and the different ways of dealing with this in terms so types in in in Ruby and in you know the
00:07:03.080 Ruby ecosystem comments questions like if you see something and you like it if you see
00:07:08.759 something and you don't like it like hoping I'm hoping that we can have a discussion about this stuff as I as I
00:07:14.520 present options that people can uh um you know react to
00:07:21.080 them so I want to start with like a really basic question what's a type
00:07:33.800 how many btes you need to allocate a type is how many btes you need to allocate being that's the first one thank you uh uh not audience plant
00:07:43.879 um initially I think historically initially this is what types actually were for and if you look at C or
00:07:51.199 assembly or something like that that's basically what you do if you do if you declare something to be an INT in C
00:07:56.639 you're not really making a strong statement about what kind of value that is you're making a statement about how much memory the compiler should allocate
00:08:03.560 when it sees it you're also to some extent making a statement about how The Interpreter how the compiler should behave in interpreting the the the
00:08:11.080 values that come into it but especially it is a q it's Q's uh to the compiler to
00:08:16.680 The Interpreter that's how that's like the I think the like oldest meaning of type in programming what's another way
00:08:24.919 to define Type A that you're drawing
00:08:36.479 wow I don't have any there are the only two reasons I have so if you have other definitions actually that's not sure I
00:08:41.519 have another one if you have other definitions like I've run out of magic uh but yeah so I think this is the way
00:08:49.160 we understand it in uh typescript in uh
00:08:54.560 crystal in Java uh if we declare something to be an integer or p python
00:08:59.680 with type hints what we're saying is we are limiting the kinds of values that we expect to come in an integer can have
00:09:06.360 the value three but it cannot have the value Fred um uh some languages allow you to make
00:09:13.320 very fine distinctions along this way you can some languages allow you to say this is going to be an even number or this is going to be a number greater
00:09:19.560 than zero like Java does not allow you to do that but there are languages that do that um I think you can force
00:09:25.600 typescript to do that you can coer typescript into doing a lot of stuff if you're willing to put in a little bit of
00:09:30.839 extra work um uh and you don't care what it looks like um uh but yeah that's
00:09:37.399 another that is another like extremely useful definition of what a type
00:09:42.800 is here's so here's a like a question then given that does Ruby have
00:09:52.120 types if those are the definitions okay I hear I hear not I
00:09:58.079 hear not somebody somebody make the case for yes somebody nodded I heard muttering I
00:10:05.760 heard muttered yeses what what about duct
00:10:12.200 types if it okay it's Define a duct type if a quacks like a
00:10:19.160 duck okay so Ruby has duck types where where in a sense what we're saying is
00:10:24.720 the subset of valid values that we accept are based on the methods that we are are going to call on this object in
00:10:31.240 a very local sense is that am I paraphrasing that correctly okay so that's a that's a that's a yes Ruby has
00:10:37.160 types but not in the way that not in the sense that like a v a value is not
00:10:42.320 limited by declaring the types it's limited in context by the methods that are going to be called on that value
00:10:48.760 okay I don't have a slide for that you you you beat me uh was there another was there other
00:10:54.560 anybody else wanted to make the S case
00:11:03.399 calling different objects
00:11:10.440 yeah right but Ruby doesn't enfor so the the point here is you could give a hash you could send a hash to an array and
00:11:16.120 that would violate our expected subtype but Ruby is not enforcing that necessarily like the there's not a
00:11:22.200 pre-processor in Ruby that is going to say you are sending a hash and this expects an array right not in the actual
00:11:28.880 Ruby native language that's my like Ruby doesn't have type arguments right
00:11:34.279 classes in Ruby are not exactly types if you declare a variable in Ruby
00:11:39.320 Ruby is going to more or less allocate the same amount of memory no matter what you put in there it's going to allocate
00:11:44.639 a pointer to an object you know with respect to various weird things that it
00:11:50.440 might do for optimization but conceptually pointer to an object um uh and Ruby does not uh
00:12:00.040 by its by its General nature Ruby is not um validating what arguments what these
00:12:07.040 are what those arguments are except Again by calling methods on it every single method is a is in its own sense a
00:12:13.160 type validation okay um this I got from Joel Drapper uh
00:12:20.959 who we're talk about this a little bit more um uh because this is part of a new
00:12:26.240 gem called literal that we're going to talk about a little bit more in Ruby a type is any object that
00:12:32.199 responds to Triple equals with a truthy or falsy value so classes in Ruby are
00:12:37.399 triple equal true to instances of that class and he is sort of backfilling that
00:12:42.720 to say that triple equals defines a type also the ghosts are trying to get
00:12:49.199 in don't worry about it pretty sure we'll keep them out uh so yes no do people buy this does
00:12:57.240 this does this work for people I see like a I see a I see a what that's
00:13:05.279 a lot of things yes so specifically he is saying that procs Define a type and the type
00:13:12.199 and that type is made up of all the things that return true against that proc yes he's he's actually explicitly
00:13:19.040 saying that at some point we'll talk about literal and I'll say what I'll show why he's explicitly saying that but
00:13:24.480 but yes he's explicitly saying that a proc defines a type in that subset of values
00:13:30.480 sense we had a somebody wanted to elaborate on they like une with this definition there's also going to be
00:13:37.040 about the we just saw in who wants to be a ruby engineer yeah proc triple equals
00:13:42.680 is call and so it depends on the definition of the and it and and regx
00:13:49.079 yeah regx is our triple equal as our ranges uh as our directories I used to
00:13:54.320 at one point know all the things in the Ruby standard library that defined triple equals because I had to know it for writing them down purposes but I
00:14:00.680 have forgotten it um uh but but yeah there's a bunch of things there's a bunch of things that are um Define
00:14:08.720 triple equals that do not necessarily correspond to our Instinct about what a type is and I'm asking you
00:14:16.120 to sort of hold on to that discomfort like what definitions and models there's a
00:14:22.320 there's a saying about models modeling in any case that a model can't really be true or false it can be more or less
00:14:28.720 useful right and sometimes that's true of definitions too definitions can be false
00:14:34.399 um but definitions in in this kind in this sense can also be more or less useful so I want to start think about
00:14:40.959 what is useful about this definition of what a type is what does it allow us to do if we think about types in this way
00:14:47.759 in Ruby and we will come back to that this isn't really a talk about literal but we are going to talk about literal at some point we're going to come back to
00:14:54.600 that we good so far yes no maybe this this what you signed up for I really
00:14:59.680 appreciate you coming out here at starting at 3:45 on the end of the second day of the conference um um and
00:15:06.600 and I strive to be worthy of your time and attention so okay there's two type of
00:15:13.279 systems in Ruby I want to get kind of a sense of the room here how many people here have used sorbet at
00:15:19.800 all so that is it's so hard to tell it's like a quarter of the room a third of the room how many people have used it in
00:15:26.199 a production system the same group
00:15:31.959 actually how many people have used it in a nonproduction no um how many people have gotten extremely angry at sorbet at
00:15:38.680 one time or another that's more people than said that they used it he just mad
00:15:44.519 at its existence or or maybe you just like went to you went to 7-Eleven and got you got
00:15:51.480 to Baskin Robins got a sorbet and like and you're now very confused um um so that's about a quarter
00:15:58.160 of the room people who have used sorbet what do you like
00:16:03.440 about it nothing great this is this is going
00:16:09.040 to be a very easy okay yeah um so it's nice that you can
00:16:17.399 well as you're experimenting with something you can write all of it without your types and then once you're
00:16:23.279 sort of ready to set it in a bit more Stone you just apply them over the top so incremental typing I don't have to
00:16:28.639 repeat that because I got caught on microphone uh incremental typing uh somebody else said something over there
00:16:43.680 yeah um this is a more rudimentary answer but it's nice that you can uh
00:16:51.240 Express whether or not you expect something to be nil um so that kind of constrains what uh yeah I guess the
00:16:59.639 arguments of a function can be yeah uh does anybody have an answer that
00:17:06.400 substantially different from that no if you evaluated sorbet and you
00:17:11.679 or you don't like it what's the thing you don't like about it nothing all right cool I got plenty
00:17:18.839 of things I don't like about it so autogenerating RBI files
00:17:25.360 autogenerating RBI files so that's sort of an implementation detail of it like uh
00:17:32.120 uh the way that it interacts with the rest of the world okay so for those of you who don't
00:17:38.559 probably should have done this first but for those of you who don't know what I'm talking what we're talking about uh sorbet is a type checking system in Ruby
00:17:45.160 it is written in pure Ruby it's an external gem it's actually sort of a a uh ecosystem of gems and this is the the
00:17:53.280 group that I was talking about they were using sorbet this is what it looks like this is sorbet for uh I decided to put
00:17:59.159 all the stuff in slides rather than go back and forth between code editor um this is what sorbet this is what the
00:18:04.600 stuff looks like in sorbet um uh you have to extend a module called
00:18:11.600 t uh colon Sig which contains all the signatures and then you get this method called Sig uh which takes a block which
00:18:19.159 defines all of the parameters here U just like do people have questions about
00:18:24.640 what this is doing uh you
00:18:32.120 huh okay so okay so this is specifically make this is specifically allowing you
00:18:38.120 to make claims about the inputs and outputs of this method so I am saying
00:18:44.360 this method takes pams it takes a buyer param that expects a person it takes a recipient uh uh it takes a recipient uh
00:18:52.400 attribute that takes a uh that also expects a person uh it takes a line has a line items argument that is expect an
00:18:59.039 array of line items and it has a promotions argument that expects an array of strengths and I'm saying void
00:19:05.120 which is to say that I don't care nobody should use the return value of this it's this is Ruby it is returning a value but
00:19:12.400 what we're saying is that value should never be used okay uh that's how you get around
00:19:18.200 that's how they get around void context this stuff okay so and uh you can go
00:19:25.000 further and you can see that this is the this is the batch uh this is the batch processor and the batch processor is
00:19:31.240 also defining its own signatures for each thing and now I think it's clear that there's a distinction if if it
00:19:37.440 wasn't clear before it should now be clear that there's a distinction between these two methods the top one is taking
00:19:43.200 in a hash of string against whatever and it's returning a person the bottom one is taking a a hash of strings and it's
00:19:50.360 returning a string but the thing that it is calling expects it to be a person so
00:19:56.760 sorbet has a static checking tool that should allow you to catch this uh from
00:20:02.320 your terminal it does a static type check of of of all of your stuff to find things like this and it also has a
00:20:07.360 runtime type check tool in case something weird happens with your static type checking the team that actually we
00:20:13.200 were talking to I was talking to about this only uses it for the runtime checking they're in a situation where
00:20:19.000 they have a lot of different data coming from a lot of different sources so there's not a lot of like data
00:20:24.320 validation Beyond like hey this is the thing that comes from here and this is the thing that comes from here so most
00:20:29.720 of what they need to do is put a label on something and be able to trace that label through it so sorbet is actually very effective for them in that use case
00:20:37.000 I don't know whether it's the thing I would choose for them in that use case but it actually turns out to be like
00:20:42.039 sort of fit for it's it's it's uh a lot more than they need but it fits into
00:20:47.799 what they need if that makes sense so what happens when you run this
00:20:53.360 if you run the code you get a type error says the parameter recipient you expected to type person we got to typee
00:20:59.520 string with the value one and this crucially happens at the time of the method call so this actually solves the
00:21:06.880 problem that that we had which is that which is that it does fast fail it fails
00:21:12.279 at the point of the method call it will also catch Nils um and it will catch all kinds of stuff all
00:21:18.200 right um another cool thing about this is that Ruby mine I don't know I think Ruby LSP
00:21:24.799 also does uh Ruby mine will catch this in the editor uh which is also nice it was it was very
00:21:32.000 surprising to me as somebody who moved away from typed languages like a long time ago because I am an old person who
00:21:37.760 has been doing Ruby for a while and when I moved to Ruby one of the KNX on static
00:21:43.559 type languages like Java was that they were very time-consuming to develop in because you had an extra feedback loop
00:21:49.240 you had to call a compiler you had to do something like that now it was very surprising to me I did a
00:21:55.919 social media like poll of like what do you like about the people who like static type languages what do you like about them and one of the things that
00:22:02.200 people like about them is that they get fast feedback in their editor because the editor tooling has gotten good
00:22:08.240 enough especially and and and good enough for static languages relative to Dynamic languages that you can get this
00:22:15.520 kind of feedback in the editor um and those there some of you are saying like yeah I know like this is my whole career
00:22:21.520 it's been like this and some of you are saying yes it's magic back in the days when dinosaurs walked the Earth like we
00:22:26.799 couldn't do stuff like that um and and so Ruby sorbet is picked up by
00:22:32.440 Ruby mine and also I assume Ruby LSP uh uh and that
00:22:39.320 works but like what do you think of this like what is this like what is your like
00:22:44.360 immediate reaction to seeing if you open up a ruby file and you saw this I see a
00:22:49.440 look of distaste would you care to go on the record with
00:22:55.039 that sure just just a look of distaste okay
00:23:03.120 yeah code I've doubled the lines of code I I mean yes because the methods are short but yeah uh I've effectively
00:23:09.200 doubled or at least significantly increased the lines of code and and one of the more consistent findings
00:23:15.039 empirical studies of computer finding is that you actually bugs are actually a function of lines of code whether those
00:23:22.120 lines of code are high level or low level or type check or whatever bugs tend to be a fairly consistent function
00:23:27.760 of the number of lines code that you're right sorry you had a reaction to the error
00:23:33.919 message what did I lose
00:23:41.760 yeah okay it just right now it's just telling
00:23:48.120 you where it's telling you that the callar and the collee do not match which one is right I think it's beyond the
00:23:54.520 ability of the type system to sort of infer right yeah yeah yeah
00:23:59.840 yeah I I mean I think I'm jumping the gun a little bit but there's a ton of
00:24:04.960 validation that is not going to get caught by any type checking that we possibly are that we plausibly are going
00:24:10.279 to do right other comments on the look of this or just the or you know the sort
00:24:15.799 of feel of of thanks I think the readability is tough like with typescript everything can be defined up
00:24:22.799 top and then you can read the code and the logic throughout but having it next to the method call or the method
00:24:30.080 definition it's it's a little bit of cognitive overload I think I yeah so cognitive over I find
00:24:37.880 that it does kind of make it hard for me to glance at code and know where the the
00:24:43.399 important stuff is um whether that's just because I'm unfamiliar with it or because it's diffusing the important
00:24:48.440 stuff throughout I do kind of like to have a lot of stuff on the screen this all the relevant stuff on screen at once
00:24:54.120 and and this makes it a little bit harder I agree with that okay two more yeah guess
00:25:01.600 if I were not familiar with sorbet and I saw that I would not assume that sigd was a type signature for the function
00:25:07.559 underneath it yeah uh I would expect it's a it's a function call with a block being passed in that does something
00:25:13.480 inside yeah I think that's fair so yeah I think it's it's not uh you you kind of
00:25:18.720 have to you kind of have to either understand sorbet or have like a a an unusual internal sense of what ruby is
00:25:24.440 doing to to get what it's actually doing there so I'm not saying this is is necess necessarily a problem here but in
00:25:30.880 general um you sort of lose the ability to duct type where yeah you you are happy with any object that responds to
00:25:37.840 the methods that the the calls that that method needs and with this you're locking yourself into a you're very
00:25:44.440 explicitly and deliberately losing taking away the taking like that's not an incidental effect of sorbet it's like
00:25:50.799 a specific thing that they're trying to prevent um so if recipient becomes like
00:25:56.039 a company because you're shipping it to a company or something like that you would have to change the type signatures
00:26:02.240 and maybe that's good like there's an argument there is an argument that yeah of course if you're calling it with a
00:26:07.279 different type you should update the thing to be aware of it but but that is where you get a little bit of that like friction where in normal Ruby uh in in
00:26:15.480 unadorned untight I don't know uncut Ruby un unpolished I don't
00:26:21.559 know what's the whatever um uh you can do that without necessarily changing the
00:26:27.679 code and then Dynamic type people will tell you that has a great strength of Ruby and people who are more comfortable static types will tell you that that is
00:26:33.880 terrifying I mean like um I I I frequently have the experience of onboarding people into Ruby at time who
00:26:40.520 have never used Ruby before and you can always kind of tell who are like very into the Java ecosystem because when you
00:26:45.840 talk about they're not being types there's a very strong sense you can almost see it in their eyes there's a
00:26:51.159 very strong sense of like what is stopping me from doing any like how does anything work in this language what is
00:26:56.200 stopping this from like going off track I mean I want set off the rails but uh
00:27:01.840 what is stopping this from like what is stopping this from going sideways like immediately the first time somebody looks at it crosswise and it is like a
00:27:09.039 more than 30 seconds explanation to say like Yeah we actually do build complicated programs in this and there
00:27:14.760 are techniques but they're not the same techniques that you're used to they're just different they're different
00:27:19.880 techniques um then I showed the monkey patching and sometimes people get really have you ever had the experience of
00:27:25.520 explaining monkey patching to somebody in Ruby and they get really mad like this is even
00:27:31.039 possible um uh anyway so that's
00:27:37.039 sorb um next option RBS this is the first party solution in in in Ruby this
00:27:43.519 is part of Ruby since the Ruby 3. whatever I can never remember off hand how many people have actually used RBS
00:27:49.480 in any capacity a lonely hand one lonely hand I
00:27:56.159 would ask how many people have used it in production but doesn't really have a production footprint um uh so I'm going
00:28:02.559 to assume that this is completely brand new to all of you RBS is Ruby's first-party type system it is designed
00:28:08.240 the way it is in part because Matts did not want type information uh in the middle of Ruby files so RBS is a
00:28:16.200 separate file this is this is the RBS file for the two classes that we have had under discussion um do somebody want
00:28:22.880 to say what it looks like this is doing
00:28:30.480 monkey patching not this is this is not so this is an RBS file and it is not actually defining these methods
00:28:38.279 okay it looks like an abstract class it does doesn't it yeah it is it is almost
00:28:43.799 like a manifest it is saying what the methods are in each class and it is
00:28:49.919 saying what they like type signature is so you read this to say like I have a batch checkout class that has an
00:28:56.480 attribute that's a file name that's going to be a string I have a process method that takes no arguments and is V
00:29:01.640 returns void which means don't use the return value um it has a buyer class
00:29:06.760 that takes in order data which is a hash of string to string and returns a person has a recipient method that takes in
00:29:12.240 order data and returns a string again you see the the the uh lack of parallelism here uh it has a line item
00:29:19.039 that returns an array of line items and then the checkout action this is the same checkout math act action method we
00:29:24.200 are just here defining this is doing the same thing that the sorbet code is doing um it's just defining the checkout
00:29:29.640 method takes a person object a person object an array of line items and an array of strings is that clear is this
00:29:35.760 syntax familiar how do people feel about this syntax also notice this is a completely separate file so it does not
00:29:42.320 touch the original file it looks like a C++ header yeah I
00:29:48.960 suspect that's not completely accidental uh there somebody was there
00:29:56.039 so you're looking I'm pointing at somebody who's looking at me like and they were just raising scratching their back or something so
00:30:04.919 uh there was complaints about sorbet being like I don't want this type signature stuff in my Ruby file right
00:30:11.200 and then that's what you get with RBS but the problem there is now you have to look in two files yes right that's exactly the problem now you have to look
00:30:16.840 in two files unless you're unless the editors get really smart about it which is not out of the question like it's
00:30:21.919 it's a Sol it's a tractable problem um but uh now you have to and there actually is also an RBS inine line tool
00:30:29.240 which lets you have inline comments I'm not showing it but it has inline comments and then it it it parses those
00:30:35.039 inline comments and generates a real RBS file so it's like a rather than have RBS
00:30:40.159 use the inline comments it uses the inline comments to generate the P the file which either gives you the best of
00:30:45.760 both worlds or the worst of Both Worlds depending on what part of the world you like I guess um uh so so this is what
00:30:52.919 RBS does and um there is a static type there's not a lot of tool around RBS
00:30:58.760 there's a static type Checker called steep which I actually didn't bother running on this but I assume it would catch the same static error um it does
00:31:05.080 not have a runtime uh Checker but Ruby mine will pick it up uh if you use RBS
00:31:13.159 if you create an RBS file and you were using Ruby mine this is Ruby mine in the branch that has the RBS take my word for
00:31:18.919 it I don't really want to dig into the editor because whatever um uh but this is this is Ruby mind in
00:31:26.399 that branch and it is it is picking up that type error so what do you think about RBS
00:31:33.440 there's a is there is the reason why you're not using it because you never heard of it or is the reason why you're not using it because you don't like it
00:31:39.200 or uh having seen it you think like wow that's that RBS stuff boy oh boy uh
00:31:44.360 we're gonna use that nobody wants to be the first person to use it which I think is part yeah
00:31:50.679 like why should I invest in learning this new thing when I can write code
00:31:59.600 I mean why are you
00:32:09.799 here I consider Dynamic types kind of a feature and not a bug yeah so I mean
00:32:15.760 that comes to like the Crux of this entire like Workshop right you consider you consider Dynamics types a feature
00:32:20.840 and not a bug and some people come to Ruby and they consider Dynamic types a bug and not a feature right you have you
00:32:26.360 have succinctly and and eloquently like uh uh brought to the entire uh thesis
00:32:33.200 point of what we're talking about here but but I I I do want to continue to talk about why people if you don't mind
00:32:39.519 I will continue and and uh and uh we'll talk about why
00:32:45.519 you know what about this makes people some people think of it as a feature and some people think of it as a bug or both it can be both at the same time like
00:32:52.039 there's there's no one there's no one answer to this question as much as I would like there to be um
00:33:00.840 I guess generally I don't want to be an early adopter of something I'd like to see that there's like some steam behind
00:33:07.080 it before trying yeah there's definitely a chicken and egg problem here um nobody's using this is true I think of a
00:33:13.080 lot of relatively recent Ruby features um nobody uses them because they're not quite fully baked when they come out
00:33:19.080 because nobody uses them there's no real impetus to fully bake them and we go around in circles this is RBS this is
00:33:24.399 rators this is uh the pattern matching sort of um although more people use
00:33:29.919 pattern matching um but yeah I think that's a perfectly valid thing what I
00:33:35.919 use you use pattern matching people use pattern matching I'm not this is not an anti-pattern matching talk it's pattern
00:33:42.600 matching is lovely pattern matching is arguably a type check um you didn't like that did you yeah no
00:33:52.320 at what point does it actually apply the type check um so the there you have to use a tool so you use like steep or I
00:33:59.600 don't even remember what all there there's a there is an external tool that you run through that will do the static
00:34:05.279 type check for you okay because I I created some gems a while back and it
00:34:10.760 seemed to have autogenerated at least the RBS yeah infrastructure yes it does and I totally ignored it right if you
00:34:16.800 ignore it that's fine okay I did note I learned today I learned that there is a
00:34:21.839 project an official project under the official Ruby GitHub page that is defining RBS files for gems in the Ruby
00:34:29.480 ecosystem that don't have them um and the reason I learned this is because Ruby Picks Them Up Now um the new
00:34:35.720 version of Ruby mine that came out this week will pick them up which is cool that means you get some of this type
00:34:40.960 checking against ruby gems against calls into ruby gems without having to do
00:34:46.000 anything um which seems like a win uh you don't have to write anything and you
00:34:51.359 get an occasional warning that you're doing something that's not ideal um so
00:34:57.480 okay okay now I want to talk about ways to like not just do the type checking here but ways to deal with this inside
00:35:03.680 Ruby um ranging from like least uh impactful to most impactful and
00:35:10.079 eventually we'll get to it was a design problem um and eventually and eventually we'll get to taking a short break uh um
00:35:17.720 uh but we're actually we're actually doing we're actually doing great on time uh uh which I'm sure you're all very
00:35:23.000 concerned about my time management concerns um uh so the first option here
00:35:28.480 is just fix the caller right there's a bug here because this thing is calling it with a string when it should be
00:35:34.000 calling it with a person object just make it call it with a perks object you
00:35:39.520 know wash your hands call it a day like don't you don't need any you don't need any the Imp the existence of this bug
00:35:45.520 does not imply that you need static typing it implies that you need better tests or it implies that you need to
00:35:50.599 just fix this bug right and yeah really like that I think that is a perfectly
00:35:55.920 viable granted it seems very viable in this like dumb contrived case but I I
00:36:01.160 want to point out that like this is an incredibly contrived example and one of the reasons that it is incredibly
00:36:06.680 contrived is that is not doing any data validation on that recipient object until the last possible second I wrote
00:36:14.079 this like sample code a bunch of times and every time I was like oh because I tried to put in like nail checks and
00:36:20.720 some other stuff that that we'll talk about a little bit later and every time I did it I got to the point where oh
00:36:25.800 it's actually every time I tried to make it more of a concrete problem and less of an abstract problem I was
00:36:32.359 inadvertently like triggering the type error early enough that it wasn't a
00:36:38.000 problem anymore um so like in a real example you would be doing data
00:36:44.839 validation like what are some of the things like what are some of the things in this problem that might cause this to
00:36:50.640 fail that are not going to get caught by like whether this is we're not going to get caught purely by type checking right
00:36:58.880 whether it's the right person
00:37:04.240 whether yeah whether the person exists in the database whether the address is actually a valid shipping address
00:37:09.839 whether the ZIP code is actually five numbers you know that actually correspond to a real address um whether
00:37:16.079 those line items are actually have positive numbers of things to ship although I guess you could consider it
00:37:21.880 negative and be shipping from the recipient back to the buyer that would be a fun way to write the system um uh
00:37:28.319 you know whether those promotion codes are valid promotion codes is not going to get caught by a type check system
00:37:33.480 there's all kinds of things that are going to get caught in that are that are reasons why this code might be invalid
00:37:39.319 reason why the data coming into this might be invalid that sorbet or RBS or
00:37:44.400 any sort of type static types or Crystal or you know any sort of type system is just not going to catch right so like
00:37:51.720 one of my takes here is static typing is a piece is a kind of data validation
00:37:57.640 right we are it is it is you are specifying in the subset case or the triple equal definition of type checking
00:38:03.520 you are specifying um something about the data you're specifying that it's a
00:38:08.960 person object you're specifying that it's a string you're specifying that it's whatever but if you have a lot of
00:38:15.319 complicated data validation like if we had if we have this recipient object and
00:38:20.359 we need to know like is the name valid is the address valid is does the person have a PO Box like are they even in our
00:38:27.000 database to accept a you know do we have like a do not ship flag on this person for some reason like all kinds of things
00:38:33.200 that might be true and once you do that like the piece where it is a person
00:38:38.280 object becomes increasingly implied by all of the other data validation like the more data validation
00:38:44.800 you are doing the less incremental value you are getting from just that piece of
00:38:50.359 static typing um I spent a lot of time trying
00:38:56.200 to teach people test D and development velopment and objectoriented techniques and one of the things that is true about
00:39:02.440 teaching testing particular is that any problem that is simple enough to teach
00:39:08.839 testing to in like a short Workshop is too simple to explain the value of
00:39:14.000 testing right if you can look at the problem and like go I know this code just works like you can just look at it
00:39:20.760 and you don't need the tests the value of testing comes in a more complicated
00:39:26.000 problem where you don't necessarily know the answer you can't necessarily evaluate it uh where there are a lot of different potential ways where you could
00:39:32.440 regress and not notice it and I think that in some sense type checking is the
00:39:37.920 inverse right it is extremely easy to demonstrate as we did like I
00:39:43.599 demonstrated in like seven lines of code here is an obvious bug that is obviously caught by type checking right and it's
00:39:49.800 very easy to jump to that to say like oh type checking must be extremely valuable because it caught this obvious bug in this obvious small case but I also think
00:39:57.400 think that the more complicated the code gets there's sort of an inverse effect the less valuable that specific type
00:40:04.359 check get now type checking can have other other other points of value it can provide communication it can provide
00:40:10.960 documentation um there are other reasons to have type hint in the code base but I
00:40:16.319 think the pure v data validation be bit goes uh gets less valuable as the code
00:40:22.079 gets more complicated I think the communication value gets more valuable as the code gets more complicated which is one of the things that makes this
00:40:28.000 discussion a little tricky um so what what do do people have any
00:40:35.599 reaction to the do nothing policy here somebody want to want to take a don't do
00:40:41.359 nothing policy take a a stand as to why this is terrible or or
00:40:47.000 great or have I finally put you all to sleep it was inevitable because it's
00:40:52.599 4:30 on well I wouldn't go these the do nothing approach I I wouldn't C is to do
00:40:58.119 nothing this to me so the first when you show the problem initially yeah my head went immediately to wait there's no test
00:41:05.640 like right so this is the right a test
00:41:11.000 approach I guess yeah that's another way in the system is contrived a real a real Ruby program would likely but not
00:41:16.880 definitely have tests like this is a rake task as I wrote it it's a rake task that calls an object so the object could
00:41:23.599 have tests but a lot of places don't test their like one-off batch Scripts um so they might not
00:41:31.319 yeah I was going to say like I haven't used either of these sorb RBS but
00:41:37.400 sometimes if I'm looking at some new code and particularly if they're passing around stuff that's like a hash with a
00:41:45.680 array of hashes you know like that and I'm like
00:41:50.960 what does this take you know like sure I'm just like what what what is valid for this
00:41:57.599 fun you know like sometimes I I I would like to have and and without having to go like
00:42:03.560 okay well there should be a test that validates what this thing takes but then I got to dig through a bunch of rpcs and
00:42:09.440 and sort of figure it out I don't really disagree with any of that I do think that like this uh this method takes a
00:42:18.040 hash of arrays of hashes is something that is going to be very tough to model in any of these like any of these
00:42:24.240 systems um I mean you just threw that out there I'm just like but uh okay so
00:42:32.119 type errors like this in my experience rarely make it to production
00:42:37.640 except when they do and at some point we'll talk about what kinds of type errors tend to make it to production but a lot of these things get caught in
00:42:44.160 development or in early testing because they are kind of think of them as being loud like they're it's often very
00:42:51.160 obvious what's happening you get a an error that that and you know the actual error here that the the thing that the
00:42:57.400 that string doesn't have an address like in most cases like that will come up in
00:43:02.880 early testing and it won't make it to production which doesn't mean it's not important like you're you're using time
00:43:08.160 on this but a thing that I often hear from people who are coming from St type languages is like how do you prevent
00:43:13.559 these things from going to production and the answer is like they get prevented the way any other bug does like we do testing and we evaluate the
00:43:19.599 code um those errors like I said those errors are very visible um and I also
00:43:24.960 think that there's like a a certain lowlevel friction of dealing with types because you can't deduct type specifically in Ruby like I think every
00:43:31.520 language has every language encourages certain things and discourages other things I think specifically in Ruby
00:43:37.480 these type systems in again in my experience there's a certain like continual friction of having to deal
00:43:43.440 with writing the types out convincing the type system that you have the right stuff trying to do flexible stuff and
00:43:50.160 working with the type system that is like another case where it is often true
00:43:55.480 in programming where if there is a a a strong a lot of time passes between the
00:44:01.079 time you do something and the time you actually pay for it one way or another it's very hard to make that connection
00:44:06.319 so like if you have a really good test suite and as a result like you can add features really quick it's very hard to
00:44:12.720 make that connection your tendency is just to think I just got to be I'm just a really good developer I can develop these features super quick um and and
00:44:20.359 and similarly like the the extra cost that you pay for adding these type systems like you don't even sort of
00:44:26.640 connect it to the initial decision to do to do the static typing it just sort of becomes the cost of doing
00:44:35.359 business okay there's an option onea here yard do people know what yard
00:44:41.960 is yes okay you can you can hold your own mic to yourself what's your
00:44:47.200 yeah uh yet another Ruby documentation
00:44:52.280 yep yeah uh so yard is a documentation Tool uh there's a yard Branch if you
00:44:57.480 want to follow along in GitHub yard looks like this uh this is doing basically the exact same thing that the
00:45:02.880 sorbet and the RBS was um this is the checkout action versions uh it it I mean
00:45:09.680 I some you we can read this is is defining a Pam called buyer that takes a person a pram called a recipient that
00:45:16.280 takes a person a pram called line item takes an array of line items uh and so on and so forth um an interesting
00:45:23.520 feature about this is that the types don't actually have to be classes you can actually Define a list of methods so
00:45:30.040 you can actually Define a duct type uh in in yard as being the the correct the
00:45:36.119 correct type um what do people think of this syntax snaps is that good snap okay th
00:45:44.680 snaps and thumbs up from the corner of the room a thumbs up
00:45:57.400 sign don't always right so the the obvious problem
00:46:02.960 here is that this is there is not a runtime type check tool tied to RBS tied to yard um I think a really good project
00:46:11.359 would be a yard to RBS bridge I think that would be a really not super hard thing to do so I'm planting that seed in
00:46:17.920 in all of you people and hoping that somebody comes up with it because I I think it would actually be doable it
00:46:23.119 exists it exists it's called sword s o r d okay it exists you don't actually have
00:46:29.800 to do it um you learn something new every day uh
00:46:36.880 uh the other really interesting thing about yard uh so I I like this intax I
00:46:43.200 like it more than sorbas syntax I think it's less disruptive I like it more than rbs's because not just because it's in
00:46:48.240 the same file I also think it's more natural um and you get documentation out of it you don't get runtime type
00:46:55.480 checking but at least in Ruby mine you do get type checking in the editor I was
00:47:01.119 floored when I found that out I did not know that and I was writing the book I was writing the pickax book and I was
00:47:06.520 writing the chapter on yard and I created a bunch of yard things and then I noticed that Ruby mine was flagging type errors just off the yard
00:47:13.280 documentation and I had no idea that that was a thing that it did uh that's a thing that it does it's not actually
00:47:19.720 giving you you don't have runtime type checking you don't have static type checking but Ruby mine picks it up in
00:47:25.240 the editor and flags it as an editor um that is not nothing
00:47:31.079 um yeah they I I they must have some sort of very good type engine in the background uh inferring stuff off of
00:47:37.400 this Ruby off of Ruby code um so I think that's pretty useful I I that was almost enough to almost enough to make me start
00:47:43.880 writing documentation uh and then I thought that seems hard
00:47:50.839 um uh okay
00:47:57.960 I want to I want to go I want to what I think I'm going to do here is I'm going to quickly go through the next couple
00:48:03.359 things because they're very similar and then we're going to sort of take a left turn and I think before we do that I want to take I will take the opportunity
00:48:09.640 to give you a like a 10-minute break is that good um you are free to not come back I'm not taking I'm not taking I
00:48:17.160 mean I would like you all to come back um but I was I was advised at least in the past to to give a break and we are
00:48:23.359 more or less at the halfway point and more or less the halfway point on time so if that's okay we will take a a short
00:48:28.880 break and then you can hopefully come back it's it's it goes against my every
00:48:36.000 performer and no I want to talk about a couple things first I want to just do the next couple slides first and then we'll take a break
00:48:42.319 I'm just floating the idea of a break sorry sorry people were like I can't wait to get out of here it goes against
00:48:48.839 every performer instinct to let the crowd go but we'll think of it as an act break we'll give you a I'll give you like a hook that will keep you coming
00:48:54.680 back so okay the a thing that you could do in Ruby is you could actually like explicitly type check so what's this
00:49:00.880 code doing somebody you're all somebody's nodding somebody tell me what this code does what it's using a guard Clause yes
00:49:10.160 it's it's using a guard Clause yay for guard Clauses let's hear it for guard Clauses uh do we like this this works
00:49:16.880 this will solve the problem as stated it will solve the problem uh pretty much every Ruby style guide will tell you not
00:49:23.799 to do this uh why like what what's good about this and what's not good about
00:49:29.680 this you what are you going to do when it
00:49:36.200 fails in your production system you've recognized the problem but you haven't solved it then that's somebody else's problem you're missing the Brilliance of
00:49:42.799 this uh yeah here here here it's an operation well yeah that's
00:49:49.119 true of the sbay stuff too like it's an operation that's being done at runtime um
00:49:56.760 right that's a that's a definite downside if you're going to actually do this at scale you're adding this line you're adding this line forever like
00:50:02.240 you're adding those things that we found distracting as sorbet as comments are not going to get less distracting as
00:50:07.359 actual lines of code in every in every uh in every Ruby method and also duct
00:50:13.520 typing is a feature not a bug um we we've taken away all the duct
00:50:19.599 typing but you know this there are limited cases where this is a valid thing to do in terms of like I would
00:50:27.119 keep it away from this kind of code but there are like Factory methods we will talk about like Factory methods that
00:50:32.960 that that do use this kind of uh code this kind of check technically this
00:50:39.920 works duct type you can do this in duct type so what's this one
00:50:47.319 doing to the extent that that that most Ruby styles guid willine on this they will say that this is preferable to the
00:50:53.760 previous version what's it doing times oh for cry out
00:51:00.839 loud yes okay that's the relevant bit yes calling in L too many times this is
00:51:07.799 what I get for not running every single one of
00:51:13.359 these these errors rarely make it to production
00:51:18.760 Ben so we're check we're we're checking what method we're going to use on recipient and the problem is how many
00:51:26.640 methods do we need to check yeah so right this this has this has the benef so what we're doing here specifically is
00:51:32.160 we are calling respond to if you assume that I typed this accurately uh we're calling respond to against the address
00:51:38.799 method and again the problem here was the address method so this in a limited way solves the problem it fast fails um
00:51:45.079 it is considered better than the explicit check against the class uh because if I had a company or something
00:51:51.599 else that was a recipient that also had an address this code would work but as you point out I could be calling 10
00:51:58.319 methods on that thing do I want to respond to all of them like this this is this is there's potentially an explosion
00:52:03.680 of checks here and really Ruby's already checking that these methods response to the method when you call the method like
00:52:10.319 the only reason to do this is because you want to do the check earlier and I think that there are we we'll see that there are other ways around it okay so
00:52:16.640 yeah so we kind of got this before we get into object design here is my hook to come back from the break we're going
00:52:21.760 to talk about the four things in Ruby that are especially dangerous typewise and and we'll talk about mitigating them and I'm going to leave it here and we're
00:52:28.400 going to come back it is on my watch 440 we'll come back at 450 if that's okay
00:52:34.319 and uh I will fix my voice and get some water in me and uh caffeine and we'll
00:52:39.680 we'll come back thank you uh for your attention so far and please come
00:52:46.400 back uh anyway seriously thank you for thank you for coming back I know that this is long and I know that it is late
00:52:52.599 and um and I know that you have to listen you come back to listen to me um
00:52:58.119 so uh I very much do appreciate it so we'll get to the here here here's like my four where where I have had type
00:53:04.920 errors that get to production in Ruby um they almost invariably fall into one of
00:53:11.599 four categories anybody we maybe we'll have the same magic that we had before where we anticipate my every thought
00:53:27.720 survey says nil
00:53:33.040 nil yeah nil is obviously the biggest problem here um it is like mitigable in
00:53:39.400 many ways partially just by is often mitigated by testing and just sort of being aware of it there are also various
00:53:45.160 like patterns that we will talk about that that allow you to manage it but nil breaks the object system nil is not any
00:53:50.799 of the types you want you never actually want nil and one of the few one of the like very valid things about using one
00:53:57.599 of these type systems is they prevent nil from getting ped um at a runtime which is valuable I have it in my head
00:54:04.440 to try and work on a ruby decorator method that would be like don't allow Nils as arguments to this method that would check at runtime um I've never
00:54:11.079 quite gotten around into it but I think it's doable um okay next
00:54:19.000 one uh an and Float int and Float I've never really had a problem maker to
00:54:24.480 production with int and Float specific specifically being a problem because they generally take those are generally
00:54:30.559 don't register as type errors they just sort of register as like math bugs um um but yeah you can have a you could have a
00:54:36.920 problem with that um integer versus string um I don't
00:54:45.760 see that make it to production it's definitely a problem I think it's definitely a thing that can happen the
00:54:51.640 the one that does make it to production is string and symbol and the specific specific problem here is I have a hash
00:54:59.960 where the keys are strings my in everybody like half of you are like I'm way ahead of you all together now and
00:55:07.079 the value is coming from the database or Json so it is a string and so it will never hit the hash that has symbol Keys
00:55:15.599 how many of you are nodding set this is like a ask me how I know seriously I
00:55:21.039 have never I have been I've been doing this kind of thing for a long time and I've never said something that cause so
00:55:26.720 many sad nods in the audience is that one like oh yeah I thought we was the
00:55:33.119 only one but guess not um so yeah so this is this is a this is a problem and this is a problem that
00:55:38.799 is specific to Ruby because Ruby has symbols and most other programming languages don't at least not in the same
00:55:45.599 way they they have interned strings or they have like uh immutable strings versus mutable strings but they strings
00:55:52.240 Ruby has them as symbols and also has symbols that are not equals compatible with their related string and there's
00:55:59.760 all of this huge ecosystem that just passes strings around it's par there are cases of this that are particularly
00:56:05.079 Insidious the hash can be a cash cash values um cash values and then the
00:56:11.880 strings come in and you never hit the cash and it never fails because you're it's still working you're just not
00:56:17.440 hitting the cash just everything's like much slower than you expect and nobody can figure out why like this is a real
00:56:23.880 in some ways this is like the most Insidious one because it's is the one that is most likely to fail
00:56:30.960 quietly whereas nil often fails very loudly uh related everything is a
00:56:38.000 hash um if you pass a bunch of hashes around in inevitably you'll get to the
00:56:44.400 point where I know this is a hash but I expect it to have this key and it doesn't have this key or or some or I expect this key to be populated or
00:56:50.720 whatever um this also can have a has a tendency to be quiet um because you you may not notice
00:56:56.799 it for a long time or you may not notice it under certain situations and it is super common to just throw things in
00:57:03.039 hashes and pass hashes around in Ruby it's just like a ridiculously common uh proba definitely like overdone thing to
00:57:10.880 do uh and the last one for me and this may be a me thing is naming where I have
00:57:16.920 problems based on naming and they don't always make it to production often these get caught but they're often very subtle and uh were any of you at the chime uh
00:57:24.400 sponsor thing yesterday no great nobody was it's very exciting um I talked about
00:57:31.079 a I talked about a tool that I was doing that is managing gems um that tool has an active record object called version
00:57:36.920 which is the version number and date of a particular version of a ruby Jem it
00:57:41.960 also has the version number which we colloquially call a version as in version three of rspec uh I also they
00:57:48.799 also have a data object associated with them which gathers data about that particular version which is called version status um ruby gems has a
00:57:57.680 version gem colon colon version which has the useful feature that it sorts version numbers based on segments so we
00:58:04.640 use that too uh it also takes care that also uses pins so it has a need for
00:58:10.280 something that can represent what is the top if you have if you are pinned to Tilda greater than 3.1.0 what's the
00:58:16.599 highest version you could have it's 31. Infinity so I have a fuzzy version that can manage 31. Infinity that has like
00:58:22.839 five classes that you could all colloquially refer to as versions that might get past if so if I have a
00:58:28.200 variable if I have a parameter name that is version unless I'm being very careful it could be any of these and they are
00:58:35.559 like somewhat API compatible but they're not completely a API compatible so you can get this is like this is something
00:58:41.440 that doesn't often make it to production but it does sort of make it through your first wave of testing um and then
00:58:47.200 something weird happens and you don't quite understand what happens and we'll talk up a little bit about some of the techniques that work around that so
00:58:52.559 those are the four things am I missing is there something that somebody has seen in production type that that that I'm
00:58:59.119 missing yeah occasionally CED an object
00:59:06.559 occasionally cached an object Marshall Marshall dumped an object and then the code moves on and you Marshall load that
00:59:13.720 object and it it won't load into its old thing before I I don't know if that counts as a type error yeah it's not not
00:59:20.680 a type like yeah it's sort of in the same constellation I don't know how common it is that you would do that but I could see like I could imagine in a
00:59:26.359 case where like you are not even necessarily marshalling but you're going to like a Json you're using a nosql
00:59:32.559 database or or a Json column in a postest database and the the expected Keys have changed and I could see that
00:59:38.920 that that also failing quietly for a long time before you get weird subtle bugs um like my my general position
00:59:45.880 about this is that I don't mind something that has a chance of producing loud bugs but I do like to avoid things
00:59:51.319 that produce like weird subtle bugs um so okay so which takes us like neatly to
00:59:59.240 sort of option four which is uh data coercion um coersion is the general term
01:00:05.200 here for taking one piece of data uh that you know as one type and converting it to another type according to like a
01:00:12.119 known algorithm Ruby does not in general do this implicitly although it does in
01:00:17.480 certain cases for example if you um Splat a method object it will implicitly
01:00:23.000 convert it to an array um Splat converts back and forth implicitly to a hash um
01:00:30.359 but if you add like string three and integer three in Ruby that will be a type error other languages will try and
01:00:36.960 guess what you're trying to do and will either do the integer arithmetic or the string arithmetic depending on whim uh
01:00:43.680 or or or whatever um uh Ruby for the most part does not do that at least in
01:00:48.920 like normal um method operation but you can take advantage of
01:00:54.920 that um in your own object design you can create your own coercion functions and the idea here is rather than being
01:01:03.839 strict about the types that you let in being generous about it and saying well if somebody passes an integer in they
01:01:11.119 almost certainly mean it to be an ID I can just look it up right so the the idea here sometimes you see this under
01:01:17.480 postals law postal law which is a little bit different in in in in intent but the
01:01:23.240 idea is be liberal in what you expect it be liberal in what you what you allow in
01:01:28.599 and strict in what you put out um so you can write your code so that you are a little bit more forgiving of common type
01:01:35.319 errors and therefore you solve these problems at a design level by actually coercing coercing these objects to the
01:01:40.599 things you want to do um it can be simple um you can do stuff like this with hash um I wind up writing key to
01:01:48.000 sim a ton in my code it's a very simple coercion that makes me that that that
01:01:53.880 solves that string symbol problem it's a little bit of overhead on my part that I kind of resent having to do but it does
01:01:59.400 solve the problem um on the other hand you can also coers the entire hash if you if you think it's coming in um does
01:02:05.799 everyone know what rails Hash Hash within different access is in rails yes no so hash within different in the case
01:02:12.680 people are just nodding to be part of the popular crowd um uh where the
01:02:18.319 popular crowd that knows what hash within different accesses which is certainly the sign of never mind uh so
01:02:25.079 um hash within different access um is allows it it essentially does that top
01:02:30.760 coercion um uh for you it it it looks if it doesn't find the key if you pass in a
01:02:37.079 string or symbol and it doesn't find it as that it converts it to the other one uh and then does that look up and people
01:02:42.920 have some strong opinions about that which we will talk about in like two slides um uh and then there's this is
01:02:48.279 Gem I have not used this yet but this is a gem that just popped up like a month or so ago called hash with do access
01:02:53.839 which allows you to take a hash and treat it as as an actual object it uses method missing and various other tricks
01:02:59.400 uh to allow you to do access a hash I'm not sure I recommend it but I kind of like that it's out there um uh I would
01:03:07.599 do hash with thata access by like converting the hash to a struct or a data object but but that's me um there
01:03:15.720 are a lot of ways to be successful in software and also a lot of ways to fail uh um so anyway the idea here is to be
01:03:23.000 more forgiving of what data you accept uh when when things come in um so this is a thing I used to do in rails
01:03:28.799 applications a lot what is this doing what's this what what are these
01:03:33.960 three lines of code
01:03:40.880 doing anybody okay yeah uh looking up a user if it's
01:03:48.079 past an ID so yeah we're taking in a user or an ID if the ID comes in and it's an integer uh I'm I'm going off and
01:03:56.279 finding the user that's associated with that integer otherwise I'm just taking the ID and doing stuff um what's good
01:04:03.079 about this I've ensured that I always have a
01:04:08.559 user even if I get past an ID what's bad about this or why did I stop using this I
01:04:15.400 actually stopped I don't know why I stopped using this but what's a what's a potential problem
01:04:21.160 here if you pass the idea of not a user yeah yeah if you pass pass the idea of a
01:04:27.200 company or a you know a something else you have in here I'm completely blanking
01:04:33.720 on things in a database um um yeah uh the the problem here is
01:04:40.920 that if you pass in an ID that is not uh a a pointing to your user you can get in
01:04:47.440 a lot of uh you can get a very very subtle issue um when I was at um I can I
01:04:54.880 think I can tell this when I was a Groupon this is a apocryphal so I'm not 100% sure that this happened it used to
01:05:00.200 be that nil. ID and Ruby returned four and yeah and it also was true that uh
01:05:06.799 Andrew Mason the CEO of Groupon had user ID 4 in the database and as a result he
01:05:13.240 would occasionally get like a bunch of emails that were going to nil users uh because the nil would get
01:05:19.039 converted to a four they would do nil. ID it would get converted to four and it would send an email to them or something apocryph I actually don't know for sure
01:05:25.240 whether that happen but it was a story that we told um so that's a potential problem here this is usually the point
01:05:30.880 where someone tells me about their typescript program where they made every single object's Ida different type so you couldn't have this problem and I
01:05:37.559 think H that sounds like a lot of work uh for the occasional misstep here in a
01:05:43.760 cost benefit analysis but you act but I also think you actually can get that in
01:05:50.400 rails uh do you know anybody know what Global IDE is in rails rails Global idea a couple of nods so rails has an
01:05:58.599 official from the rails team uh gem called Global ID and it converts uh you
01:06:05.319 get an ID that is like user 37 or something like that and it can convert it to class and object so essentially it
01:06:12.599 is it is a global ID um for for whatever class you want to do and you can do a
01:06:18.319 couple other cool things with them you can um cryptographically sign them you can make them so they're only valuable
01:06:23.559 for they're only valid for like a certain time the idea is you can then take it and pass it as like um like a
01:06:30.200 password uh you can give somebody a temporary key for their password and then you could convert it immediately
01:06:35.520 back into that user ID and it is invalid after a certain amount of time it's it's actually really cool um so with a little bit of
01:06:42.480 judicious monkey patching here um what is this
01:06:52.200 doing I haven't actually used this in production but I am tempted
01:06:59.799 anyone so I'm creating a method called find if that takes in a class and I'm monkey patching it into both Global ID
01:07:06.799 and active record so you pass it a class and it does the conversion model class
01:07:11.880 is the class of the uh Global ID um I can't believe I just Ed the pointer I
01:07:17.000 feel I feel that doesn't make me feel great I got to say um and if it's not the right class
01:07:23.760 it returns a runtime error um if it is the right class it Returns the object that's basically the global ID API to
01:07:29.440 return the to check the class and return the object it's doing something similar in active record if if it's the class of the object it's returning the object uh
01:07:36.680 if not it's returning runtime error so then you can say I'm this method takes a
01:07:41.799 person or Global ID and I can just call find if on it polymorphically and if it
01:07:47.039 is a person or Global ID that is of the right class I will get the right object back and if it is not I will get a runtime error um this seems useful to me
01:07:56.359 in like a sort of type A Chey kind of way I think it would be this would be harder to misuse than the previous one
01:08:02.960 but you get a lot of the same issues you look skeptical that's fine I haven't actually used this in production so I
01:08:08.160 can't say um um the reason one of the reasons I haven't used it in production is it is a little bit complicated to get
01:08:16.000 rails to sort of use GL you want then you'd want your forms would have to pass Global IDs around and things like that
01:08:22.040 so you have to actually explicitly like turn your application into an application that uses Global IDs and
01:08:27.520 that is not like a trivial thing it might be worth it um but it is not a trivial thing so okay so you can also do
01:08:36.080 this this is now back if you go back into the code under the coercion Branch if you're following along if for some
01:08:41.159 reason you are following along in GitHub rather than just letting this wash over you um uh this is actually in the GitHub
01:08:48.199 repo um what's this doing this is part of the person class
01:08:54.400 that we have alluded to but not actually seen so
01:09:00.920 far yeah it's running down a list of ways you might get a person so uh if it's a global ID we're we're actually
01:09:07.000 checking that for the global ID um and then we're going through and and casing the object if it's an integer this we
01:09:13.199 actually are doing direct type checking here um uh which is this is this is the kind
01:09:20.319 of case where I do find it sometimes necessary or valuable to directly check against a type um the other option here
01:09:27.880 would be to Monkey patch integer and string to have a convert to person method um I would be finding that in
01:09:34.319 code that I was using on a team of me but I find that if I do something like that too aggressively in a team of other
01:09:39.480 people they don't like it and they come after me um uh but I I but like that in like in
01:09:47.440 theory what you're supposed to do here is double dispatch to integer or string
01:09:52.480 do to person um but since that would involve monkey patching and there are good reasons why we don't want a monkey
01:09:58.239 patching this is like a standin for that by the way appropo of previous conversation what is it using to test
01:10:04.000 whether something is an integer a string or a person here in the case statement triple
01:10:09.920 equals yeah so like very explicitly a type is being defined by triple equals
01:10:15.960 here so we could have a proc like you could have something here where that where you had a proc as one of these
01:10:21.120 when statements and like I don't know on Tuesday return a person object on Wednesday like you have arbitrary you
01:10:28.000 can make it arbitrary with this structure uh okay then then you would
01:10:33.320 use this as follows I'm also mapping the promotions to symbols here to do that coercion but the option the real Point
01:10:40.040 here is that both buyer and recipient are going through this from pseudo like
01:10:45.640 Factory method that is guaranteed to return a person uh or throw a runtime
01:10:51.120 error and now I know it's a person this also solves the problem it solves it in a different way rather than throwing an
01:10:56.920 error we're actually like performing the order um in general like doing things
01:11:02.960 that will cause your company to uh run to load a sale is preferable to throwing
01:11:08.159 an error just a little bit um so so this is doing so that that's what's Happening
01:11:13.719 Here we are guaranteeing that person um uh returns uh uh that from method
01:11:21.159 returns a person you could actually just type check this person this person method and just say you know it takes in
01:11:28.840 an object that is any and returns a person um and you actually can get a little bit of typechecking stuff for
01:11:35.800 basically no real cost like we know that this returns a person and if we tell it with RBS or sorbet or one of these or
01:11:41.800 yard or whatever that it returns a person we can get a little bit of benefit from that in our editor as well
01:11:47.360 some editors May pick this up but it's it's pretty sophisticated to pick it up in general okay what are some
01:11:55.760 pluses and minuses of using this kind of technique like how do you react to
01:12:01.199 seeing this code if if if if if if I passed you this as a p if I if you came I came to you and I said I was asked to
01:12:07.000 fix this bug and this was the pr right what would your comment be many
01:12:13.159 hands yes what if you get a string ID what if
01:12:18.679 you what if I get a string um yes so yeah so I right so I I I had to look at
01:12:24.480 the code like yeah yes I assuming here that a string is is an email um because
01:12:29.719 I was trying to be cute but yeah it could just as easily have a string that refers to an ID and then this would fail you're right that's a good point um
01:12:36.280 extensive disgust um no I I worked in these code bases where you never really know what
01:12:42.199 anything is and so you're always having to add person. from or whatever and you could have this thing being you know a
01:12:48.080 person's pass to it one thing pass away next thing pass next thing and each one is calling person. like just in case
01:12:54.719 because already know what anything is um yeah so that that's a potential downside of this is sort of a Cascade where you
01:13:00.120 are continually passing you're continually passing this around and you're continually right the the there's
01:13:05.960 kind of a way around this where you sort of like specify this is where you sort of get into like boundaries you like
01:13:11.480 okay this is a boundary and we're going to do our type checking at the boundary and we're just going to assume type checking here that takes a little bit of
01:13:17.280 discipline or structure or things that a large team may not be able to do easily but yeah that that's a potential problem
01:13:23.040 for this usually on something like this the the like nil the like identity
01:13:28.120 Clause is right up top so return a person if it's already a person is up top so the cost of that is the the
01:13:33.679 performance cost of that is minimal but yeah you have to remember to keep doing it and that's a potential
01:13:45.080 problem um I would kind of assume I would ask the writer of the pr why
01:13:51.600 aren't we just updating the call sites to ensure one or the I mean we might do both right you could eily do both I I
01:13:58.719 would say because in the case where somebody else makes this mistake I would prefer to log the sale than throw an error and we could argue that's a debate
01:14:06.120 like I think that's a perfectly reasonable uh like why why why this is this is adding complexity do we need
01:14:13.360 this complexity I think is a perfectly valid question to ask someone
01:14:22.960 else I just want to see people calling people as far away from Ben's current location as possible I just want to see
01:14:29.199 what a step count is at the end of the at the end of the I mean you kind of already alluded to it but my first thought is what sort of sloppy service
01:14:35.400 boundaries do you have in this application what sloppy what service boundaries yeah so the the a common
01:14:42.360 complaint against this kind of thing is that it makes the team sort of sloppy if I don't have to care whether I'm passing
01:14:48.840 around integers or strings or people or whatever then I won't care um my usual
01:14:54.320 response to that is if I don't have to care then I don't have to care and it doesn't matter but that's glib and and
01:15:01.600 it's the kind of thing that you can say if you are standing up here and not writing production code and just like talking um in practice like it is a
01:15:09.280 concern like you you know um it can it can be a problem like you
01:15:16.239 know you you could have the like passing an integer that's not a person ID problem like that could easily happen here the call is person. from so if you
01:15:23.360 see a line of code that is like person from Company ID hopefully that will set off an alarm Bell um but but you know
01:15:31.440 like you you are deliberately you're making a deliberate Choice here to be more permissive and one of the consequen
01:15:38.400 of that is that people are going to be more per like they're going to be more flexible in response to how their code
01:15:44.440 goes I guess I I would think it was okay just as long as like people are saying as a team we agreed these things all
01:15:50.760 have like kind of the same identity like maybe an email in is we allow you to
01:15:55.800 have one email attached to this account right and that can be sort of like an alias for the database ID because if
01:16:01.800 we're going to be like well we need to always use the same thing then you're going to have to ask people to like I
01:16:07.159 don't know in testing and thinking about it as human beings always thinking this ID this like long number which is hard
01:16:13.000 for people to do I think that's one of the reasons these types of bugs maybe happen yeah I think the email here is is
01:16:18.840 um definitely possibly a bridge too far to throw the email in here um it definitely has a little bit of a one one
01:16:25.000 of these is not like the others um okay Stephanie
01:16:33.320 you uh could be nice if you're consuming from one or more multiple one or more third party services that you don't control yeah right right in some cases
01:16:41.080 your goal is to try and be resilient may be like an overly strong term for this
01:16:46.719 but in some sense what you're trying to do is make your code resilient like I I can they can throw me whatever weird
01:16:51.800 data and I can convert it to the thing I want the side effect is that is that sometimes you may make a mistake and not
01:16:57.239 convert to thing you want um and and it is like a a caseby casee basis as to
01:17:03.000 whether that's more important or less important than just throwing the a I can imagine cases where either would be
01:17:08.199 true um so there's a specific thing about hash with IND different access I just
01:17:13.880 saw somebody have like a rant on Twitter or something that got passed around about how hash within different access was by itself a code smell um and and my
01:17:21.880 response was like I have been a rails developer for Mumble Mumble years um
01:17:27.280 I've been a rails developer for almost as long as there's been a rails hash within different access has been a part of rails since that time and I have had
01:17:34.239 precisely zero problems with it I would bet that there's a significant percentage of rails developers who use
01:17:40.920 pams hash all the time and do not even think that there is a thing called hash with indifferent access beyond that they
01:17:48.080 just use it and to me that's great like I don't have a problem with that um that
01:17:53.920 said like you would think that the implication of that is that I would often use hashw in different access in my own code and it actually turns out
01:18:00.679 that I don't and I couldn't give you a real good answer why I tend to use the explicit conversion to symbol rather
01:18:07.440 than explicitly use hash within different access in my own code I don't have a great reason for that other than
01:18:14.639 perhaps I internalized the idea that hash within different access was a code smell and and never really thought about it um I don't I I can't I can't
01:18:22.320 sometimes I can't explain my own actions um but yeah there's a specific like this is like this is a specific kind of
01:18:27.920 coercion that is real easy to do has almost no chance of having a negative
01:18:33.280 side effect and yet at the same time some people will be like no no that's that's too flexible um I I don't know I
01:18:39.639 find that to be weird Okay I want to the next couple
01:18:46.480 things here talk a little bit about more uh about objects what do I got like got 28 minutes we're going to we're going to
01:18:53.360 power through this um let's talk about objects what's an object in the same way of like what's a
01:19:01.080 type generically what's an object oh sorry people went to sleep I'm
01:19:11.320 sorry that's good make Ben Run next I want somebody up front to answer the next question an object is uh bundle of
01:19:20.000 code data and the things that you can do to that data yeah data and functionality
01:19:27.040 together right um so Ruby is an objectoriented language it is made of objects these
01:19:32.960 objects contain data State more generally and the the the functionality that that adapts to and and adust
01:19:40.320 adjusts that state why is an object why is Gamora um why why are
01:19:48.320 object why are objects considered I would say why are objects considered good but that's not really the question why are objects useful
01:19:57.760 gives meaning to bundle of data gives meaning to bundle of data that's that's definitely true um it is not the thing I
01:20:04.239 have on the next slide I'm sorry but is unquestionably true it and is not nothing like having a name having all of
01:20:10.480 these things together and calling them a person rather than just calling them a random hash that has a bunch of things
01:20:15.639 has definite communication value and doing that well and doing that poorly makes a big
01:20:23.480 difference yeah you there you're the only person who's even close to raising your
01:20:30.320 hand composing
01:20:36.679 Primitives um kind of yes but it's not really what I'm looking for here now
01:20:41.800 we're asking you to mind read me but somebody will do it yeah
01:20:47.800 I limits the scope the fancy technical term for that is encapsulation oh okay so
01:20:55.280 never mind what somebody's like I was going to say encapsulation darn it um
01:21:00.960 the the the so the advantage of encapsulation is that it limits what
01:21:06.880 code needs to know about other code one of the things that makes code hard to update is hidden dependencies I change
01:21:12.960 this code and I can't tell what other code Downstream uh is dependent of that is is is dependent on that um if you
01:21:20.000 have stuff in methods in an object callers of that method at least in theory don't need know how it's
01:21:25.120 implemented and they don't and they don't have a dependency on the implementation of it so you can more safely make changes Ruby in particular
01:21:32.080 is designed to make certain kinds of encapsulation really easy in particular Ruby makes it super easy to go from uh
01:21:38.159 something being a like an attribute like a simple reader to a more complicated uh method without the callers having to
01:21:45.159 care like you can change things from local variables to methods without having to worry about it and then there are certain kinds of refactoring that
01:21:51.080 make encapsulation easier that Ruby's really good at okay so my one of my uh things about
01:21:59.679 Ruby is create instances and create classes and create instances of this
01:22:04.880 class of these classes so just from a design perspective I realize it feels like I'm going a little bit of field I
01:22:09.960 promise we we'll pull this back in a second from a design perspective if you have a service class service object in
01:22:15.800 Ruby there's broadly speaking there's three different ways to access that you can create a class method or a module
01:22:23.040 method that does doesn't create an instance you can create an instance but create an instance but pass the data
01:22:29.080 only pass the data at the point of call or you can create an instance and pass the data at the point of instance
01:22:34.520 creation and then have the call method um create the call create the call I
01:22:40.560 submit that like 120% of the time you should do the third one I don't often
01:22:45.760 make like strong pronouncements about what you should and shouldn't do in Ruby because what what do I know and what do
01:22:50.840 I know about your life and your constraints um but the B one is like a million times easier to test because you
01:22:57.360 can separate object creation from the actual object functionality it is a billion times easier to refactor because
01:23:03.000 you can refactor the call method without caring about the things that are getting passed into it and it is easier to use
01:23:09.560 for both of those reasons like the cost of change of that bottom level that bottom one goes goes um is way easier
01:23:15.920 than the top one other languages like Java in particular is sort of optimized for that top one in a way that Ruby is
01:23:21.800 not um it's easier to refactor that in Java in Ruby my experience is that like
01:23:27.639 creating instances and calling methods on instances is like a really clean way
01:23:33.480 to go and it will improve your code and among the people who disagreed with this this on social media with me about this in social media today was Dave Thomas
01:23:41.960 so uh it was very exciting that Dave Thomas made his first post on blue blue
01:23:47.360 sky specifically to tell me that he disagreed with me about something it was great it wasn't at all
01:23:53.360 like a waking nightmare I definitely D so Dave's point was the Dave's point which is actually not more
01:23:59.320 orthogonal to my point than than contradicting it I'll say um Dave's point was that you should push stuff out
01:24:05.440 of objects and he he has come to prefer a more functional style of Ruby and that can work too it's towards the same thing
01:24:11.600 of separating out of of like making smaller pieces and building in encapsulation he's just doing it with
01:24:17.360 functions rather than objects we that's fine I'm not hung up on it that's why
01:24:23.239 that's not why I brought it up don't put in the paper that I'm mad I'm not mad
01:24:29.120 um uh so anyway so the to pull this back right we can refactor this code in a
01:24:35.760 couple of different ways to make the to make other changes easier to do right so
01:24:42.239 if we pull this this is the same checkout action I have just pulled I've done two things to it I have pulled the
01:24:49.520 data the like parameters into an initialize and I'm creating instance variables and I have also rather than
01:24:56.159 have a service that calls a service that calls a service it's all manifest in the top level I firmly believe that you are
01:25:02.360 generally better off if you have methods that are leaves and methods that are nodes and you don't have methods that do
01:25:08.360 both uh and that was a problem in the existing code you had a method that was both like doing a thing and passing
01:25:14.280 further on um in general I find it Like A good rule of thumb and factoring that
01:25:19.639 is a good idea to have uh one method that like calls all the things and other methods that do the things that get
01:25:25.600 called and not mix up those levels of abstraction so this is I I I hope this
01:25:31.639 looks a little bit cleaner in terms of like understanding what's actually going on and it also makes it easier to see um
01:25:37.760 the it kind of makes it easier to see where the shipping is and where it might make it easier to see that type error up
01:25:44.440 front this is why this is actually why my initial reaction to the code was it's a design problem because I wanted it to
01:25:49.800 look like this and it was presented to me as a thing that calls a thing that calls a thing I was like I would never do that why would why is that a problem
01:25:56.040 um so okay this is easier to test it is easier to refactor another thing here is
01:26:02.040 there are very few times in Ruby where you can ensure that something happens before something else but one of those
01:26:07.280 times is you can ensure that an initialized method is called before the meth any other method is on that on that
01:26:13.080 method is used so if you have data validation that you need to have done on an object you can do it as part of the
01:26:19.280 initialization process and you will guarantee that that initialization happens before any other uses that there
01:26:25.000 aren't that many times in Ruby where you can say that and this seems like a really good one to take advantage of I
01:26:30.320 can you can do my dat you can you're essentially creating a data boundary at the initialized method of the object so
01:26:37.360 that you can you don't have to type check everywhere else in the object you type check when the object is created
01:26:43.040 and you know the object has to be created before it's used okay so this brings me to I'm
01:26:50.679 actually going backwards a little bit this is literal so literal is a gem from Joel Drapper it
01:26:57.000 got released like a week ago so I scrambled to put it in because I thought it was interesting literal allows you to do
01:27:04.320 runtime checking on the creation of new objects exactly as we were talking about and what it allows you to do is it
01:27:10.360 allows you to specify it has it's very flexible to allow you to spell specify custom coions of validations so here's
01:27:17.000 what literal looks like um without knowing what literal does since I assume nobody here has used
01:27:23.679 it because it just came out um what does it look like this is doing like if you had to if you saw this
01:27:30.920 in a PR what would your instinct be that it's
01:27:43.920 doing um it's using a DSL to Define which attributes are expected and
01:27:50.400 handled at initialization and to make assertions about uh the nature of those
01:27:57.560 arguments and what can be done with them in terms of like the the reader yeah I think that's about it does
01:28:04.840 that make sense to everybody um I'm specifically specifically what it's doing is it is um it's calling these
01:28:11.600 things as properties it is essentially giving me notice I don't have an initialized method defined here um it's
01:28:18.639 it's defining an initialized method for me that takes all of these by default as keyword arguments I think think but you
01:28:24.520 can specify um and it is doing it is doing a type check on creation of the method um
01:28:34.199 guess what it's using to do the type check triple equals what does that mean you can put
01:28:40.880 in a type check any proc so if you wanted to type
01:28:46.480 check that this only took even numbers or if you wanted to type check that it only took a integer greater than zero
01:28:52.040 for a money you can actually do that in literal um it's extremely flexible so it
01:28:57.600 has underscore array here defining the array is actually a method it's actually a method that returns a proc um and so
01:29:05.159 it's triple equaling to determine it has a triple equal test that determines whether the incoming thing is an array
01:29:10.800 of line items and they're coming out with something that will preserve that type check on all of the array
01:29:16.239 operations that you would do afterwards which is not part of the gem yet but will be released soon
01:29:22.320 um so how do people feel about this is this good bad and different like if somebody if I came to you and I was like
01:29:28.760 I saw this really cool thing in Ruby weekly or whatever and I think we should use it everywhere now like what do you
01:29:39.280 think I got a thumbs up I got a hand
01:29:47.320 yeah gets towards kind of a parameter object or a value object yeah right and
01:29:53.360 so I'm just going to have that and that's going to be separate from the rest of the log yeah I think that's fair
01:29:59.480 it's going towards a parameter object or a value object I think that's kind of an explicit design goal of this is to allow you to create these kinds of Val it
01:30:06.280 actually has um this is object but it has an override for struct and for data
01:30:11.320 as well so yeah that's very explicitely it um I I I haven't used this in
01:30:17.119 production yet um I am cautiously optimistic about it as being like a nice sweet spot here where I get very
01:30:24.159 flexible checking but not everywhere but at particularly useful boundaries I don't
01:30:30.239 know it just came out last week nobody knows um uh but I thought it was cool
01:30:36.000 and I thought it was real particularly cool because it shows off how you can take advantage of Ruby's flexibility to
01:30:41.719 do something that looks a lot like static type checking but is actually quite
01:30:49.000 Dynamic so so so anyways this if you were to run this now you would get a runtime error uh at the at the creation
01:30:56.840 of the object which means in time to solve the problem and this is the error that it gives you expected a person it
01:31:03.080 gave it an integer actually should be a string but I updated some of the code and not other of the code
01:31:10.159 uh so okay um I'm going to quickly go through this one because I think it's
01:31:15.800 kind of trivial and then I want to focus on the last one and we're kind of running out of time um one option here
01:31:21.760 is you can extract a data object object we talked about value objects this is kind of very strongly overlapping with
01:31:27.960 things we already do in this particular app example but you could create a checkout model that takes all of those
01:31:35.239 line items and then has its own validity and its own create order and its own subtotal and stuff like that and then
01:31:40.320 you can use that so you could the this is the controller it's creating that checkout model and then it's passing it
01:31:47.320 to the action rather than passing all four parameters it's passing the the the object um and then calling the checkout
01:31:54.239 object so the the actual object looks like this it takes in one model and thrown in a success attribute um and
01:32:00.199 then it just keeps passing that through the rest of the the values this is like I consider this sort of a minor
01:32:06.960 improvement over the previous one um because it gives you that sort of initialization check on this object for
01:32:13.639 data validity um I don't know that in this particular case I don't know that it buys us a lot I think that there are other cases where it could one thing
01:32:20.560 about this is that because the same object is going to all the subactions you can then call the
01:32:26.760 subactions on their own and still get the data validation because the data validation is a function of the data
01:32:31.880 coming in and not the service that seems like a mild Advantage um uh because you
01:32:37.480 can guarantee the order of it another thing is that these model classes um you can see I've already
01:32:42.639 thrown in a valid object and a bunch of other things they tend to sort of attract useful logic in ways that stuff
01:32:48.639 that feels awkwardly attached to one of the services tends to seem to to my mind more natur naturally attached to a data
01:32:55.040 object um your mileage May different I I I generally find that when I create an object like this I usually find it
01:33:01.400 useful I I I usually think that usually sort of justifies itself in term of of logic that winds up going there that's
01:33:08.840 not actually the thing I want to talk about we're kind of running on time I don't want to spend a whole lot of time on that one um but I do want to talk
01:33:14.400 about like we talked about what is an individual object um what is the entire
01:33:21.880 object system in Ruby like if you were to talk about what
01:33:27.679 uh a way that you can conceptualize like a method lookup in Ruby what is that sort of abstractly
01:33:34.920 what is happening when you do a method lookup in Ruby the ENT over the entire object
01:33:41.280 system not sure how deep you want to go but not sure how deep you want to go
01:33:47.119 there's there's a lot of uh delegation up an inheritance tree yeah I I I what I
01:33:54.400 kind of want to go is there's a lot of Delegation up in her industry the abstraction that I want you to think of
01:34:00.040 is the entire object system is sort of a case statement if I if I'm doing a method lookup it's not implemented this
01:34:06.800 way but you could think of a method lookup as being a case statement if the receiver is an integer then I call the
01:34:12.920 integer method if the receiver is a string I call the string method it's actually doing this more directly because it knows what the receiver is so
01:34:19.080 it's actually it's actually not doing it like this but you can think of this like this um and you can think of the fact
01:34:25.719 that this turns it into something where we're using triple equals as type checking and you can also think of this
01:34:33.000 as something that allows you to offload some of your state and some of
01:34:38.080 your uh conditional logic into the object system rather than having this
01:34:44.199 this is sometimes called if lless programming um rather than have continual if checks against the same
01:34:50.280 logic you do the if check once and you return two different objects that have different behavior that are called
01:34:55.600 polymorphically does that make sense an example there's a couple of couple of puzzled looks um so let's
01:35:03.800 let's talk about this the the idea here is something that you might call a constellation of objects um so I just
01:35:10.280 threw in a couple of extra pieces of logic here so this is the same code with a couple of extra things um so I would
01:35:15.960 imagine like perhaps I want to log Nils in my checkout uh the buyer is nil I
01:35:22.040 want to log the nil if the recipient is n I want along the nil maybe I want to create my subtotal based on uh the tax
01:35:29.360 status of the buyer whether it's us tax I don't know I made this up just to have a case statement here right so I have an
01:35:34.760 if statement here based on the state of the world whether the buyer or the recipient is nil I have a case statement
01:35:41.280 here based on the state of the world based on the tax status of the user and I want to suggest as like the null
01:35:48.440 object pattern does anybody know what the null I'll show what the null object pattern is and then we can we can talk about whether this makes makes whether
01:35:54.119 my explanation makes any sense at all um what you can do is you can
01:35:59.520 replace these conditionals with classes so you could have sort of a group of objects so rather than having a person
01:36:05.800 you could have sort of the the main data is stored in person but it is surrounded by an object that is like a null buyer a
01:36:13.119 null receiver a buyer person and a recipient person all of which have separate roles in the system and you
01:36:18.480 might have logic that depends on whether something is a buyer or a recipient you might have logic that depends on whether
01:36:24.360 something is null or not and by offloading that logic into the object system you can have the actual code that
01:36:31.199 uses that logic uh be cleaner uh and and a little bit more clear as to what
01:36:36.360 you're trying to do at least that's the theory so you can do something like this so what is this doing
01:36:54.639 anybody yeah what it's a factory that's true what does that
01:37:10.400 mean yeah it's depending on what was depending on what class to sorry determining what class to instantiate
01:37:16.840 based on the input so this is returning a new class based on the input the input
01:37:22.119 here is whether it's a person or a nil or whether it's a buyer or a recipient and it's creating one of four classes
01:37:28.520 it's creating a null buyer a buyer person a null recipient or a recipient person does that make
01:37:37.119 sense people looking at this nervously um so then you can do
01:37:42.480 this so what is this doing
01:37:56.159 yeah it's safely hand safely handling your edge cases was what you said and I like the word safely there um it's maybe
01:38:02.520 uh generous to call it safely but I appreciate the I appreciate the generosity um what it's doing is it is
01:38:09.920 these these classes are these objects are now all API compatible right they all return a method called Nish they all
01:38:16.480 have a method called Nish they all have a method called log identity right and
01:38:22.199 Nish the hell uh Nish returns true for the
01:38:28.239 null objects and false for the quote unquote real objects the real objects all use Simple delegator to back onto
01:38:34.199 the person object so that anything that you call on that object that is not defined will go will automatically be called on the person object um huh oh
01:38:42.880 sorry uh and they everybody's defining a log a log identity note that that log identity um the buyer and recipient is
01:38:50.639 not is literal and not dynamic uh it's static the name of course is static is
01:38:56.639 dynamic and then so rather than have this code sorry this code with
01:39:03.040 this conditional right you have a code here
01:39:08.639 uh that looks like this so I I
01:39:16.000 um it this didn't call the it didn't call they both need to call the role person and it's not in this line
01:39:28.040 sorry it's we'll just keep going um they both need to call the RO identity to return the new object and it's not uh
01:39:34.719 it's doing it in the it's doing it in the um initializer and I didn't put the initializer in the slide um do it still
01:39:41.560 clear what's happening here it's calling log identity which also a thing to note here is that the the null objects log at
01:39:48.719 worn uh level and the uh buyer log at debug level which means under normal circumstances the the null objects will
01:39:56.360 log and the non-null objects won't but you can switch it um and then you're
01:40:01.520 breaking it out if either one of them is Nish uh and then you're continuing forward the idea here and I'm not sure
01:40:07.080 that this is the best example to show it is that the actual logic of this method
01:40:12.119 is clearer and the edge cases are being handled in specific objects um you can do similar similarly
01:40:19.920 you can do something here with the tax rate object where you create us untaxed
01:40:25.360 tax exempt us tax rate or Canadian tax rate objects um that in this case I'm
01:40:30.719 just creating small data classes they all have their own tax method and now rather than that cash statement um I'm
01:40:37.719 return I'm grabbing the tax rate object in this case I actually am did have the code here right um it's creating the tax
01:40:43.000 rate object and it's just calling tax directly on that object so the case statement with the complexity of how to
01:40:48.880 classify these things is separate from the uh how do you use this thing so you have a very clean how do I use this
01:40:55.520 thing and the and the part that's complicated is is off to another thing
01:41:00.920 okay so uh if somebody if somebody handed you something like this in a PR
01:41:06.440 how would you react thumbs up
01:41:12.440 nods Ben is skeptical it is adding complexity um
01:41:19.679 it's also taking away complexity uh I have tired you all out you are out of
01:41:25.760 opinions that is good because we are at the basically at the end I just need one more
01:41:36.000 opinion what matters to understanding what's going on is very clear here because you've taken away a bunch of
01:41:41.840 this checking and validation yeah I I have to say I really like null object
01:41:48.040 and sort of these related things in that gem dashboard code that I was doing use this pattern EXT extensively um I have
01:41:54.800 like a null I have a lock file class that purses lock files some of our gems don't have lock files I have an implicit
01:42:00.880 lock file class um that that guesses basically but they're API compatible so
01:42:06.800 the rest of the code doesn't have to worry whether the gem has a lock filing off it just calls stuff um so I have stuff like that all over it and and for
01:42:13.159 the most part I feel like it has really cleaned up the code and made it like easier to manage once you get over the
01:42:19.960 hurdle of like everything is an indirect away and that's kind of the downside of this
01:42:25.360 everything is an indirect way and one of the critiques of this kind of object-oriented programming is you can never find where anything actually
01:42:32.159 happens because everything redirects to some other object that redirects to some other object and there are advantages to
01:42:39.599 that and one of the advantages of that is it actually becomes quite easy to test edge cases because all you need to
01:42:45.119 do is create a class an instance of your Edge case class and pass it through um
01:42:50.800 but there are also disadvantages of that because you know some it's it is hard it is a
01:42:55.920 little bit a lot of people learn objectoriented programming without learning these
01:43:01.119 techniques uh they learn object rning pring just through the the syntax and like the bare semantics of it and so the
01:43:08.800 first time I think somebody is introduced to a technique like this there's a tendency to think like oh that's really complicated um and and I I
01:43:16.000 don't know whether it is in practice all right we have like only a couple minutes left and uh I wanted to get like I I
01:43:22.080 wanted to do you seem out of opinions but if people have like uh want to say like uh pros and cons of static typing
01:43:28.360 here or or heck we got like three minutes left I won't torture you anymore here's my
01:43:33.599 list um if people have things that aren't on here so the things that I think are in favor good points about
01:43:39.159 static typing uh it does still have better editor tooling it may not forever but it does right now um and static
01:43:45.760 analysis tools um the code doesn't need to handle edge cases so all this stuff about
01:43:51.199 handling nails and null objects and stuff like that that all kind of goes away in the right static typing system
01:43:56.280 because Nils are just errors they just don't happen which also means you need to write fewer tests and it also means
01:44:02.920 that you probably have more information on the page for the reader about what the intent of the code is to me those are the positives um I think there are
01:44:10.520 negatives I think some people like to say that there aren't negatives of being stricter in typing I think there are I
01:44:15.719 listed a bunch of them I tried to make the list long um uh static typing doesn't catch all
01:44:22.560 your errors and it may not catch the most important ones um if we switch the buyer and the recipient in this code
01:44:29.080 ain't nothing going to catch that like that's that's just a problem that's just a bug I can't believe I just said ain't
01:44:34.239 nothing I'm very loopy at the end of this uh at the end of this uh and I
01:44:39.360 apologize deeply um uh you're usually in Ruby you're adding an additional tool
01:44:46.280 you're adding an additional type Checker you're adding uh uh you're adding something uh else you're adding a
01:44:53.400 dependency whether it's sorbet or RBS like there's there's an additional depending there's more typing in the physical sense that you're typing more
01:44:59.440 characters um which can lead to more bugs and more Tire more tiring uhhuh yeah okay fine um and sometimes you have
01:45:07.400 more complexity sometimes you have to add complexity to the code to convince the compiler of something that you already know about the code but the
01:45:14.280 compiler or the static type system can't figure it out you know this thing's not going to be nil but you have to convince the compiler of it you know that this
01:45:20.960 thing is something but you have to explicitly pass it that can be a pain um it can be harder to test static typed
01:45:27.800 code because it can be harder to create Edge case conditions like there there's a real problem here that is like I kind
01:45:34.080 to call like the 99% problem sometimes you put constraints on code because you think they're 100% constraints and they
01:45:40.119 actually turn out to only be 99% constraints and then you have real problems um because there's no way to do
01:45:45.960 this thing that you have to do because you modeled it as being an impossible situation that is super easy to do in
01:45:51.320 static typing um and and you need to sort of argue against that you need to work it's hard to test against that uh
01:45:58.000 specifically with impr Ruby a lot of metaprogramming and flexibility is harder in a static type environment that
01:46:03.440 is by Design like they're trying to make it harder um and this is like a hot take
01:46:08.800 that I can't quite prove my experience here is that people who are like heavily
01:46:14.679 used to static type languages tend to be less aggressive about object-oriented design and so you tend to have like
01:46:21.880 longer methods more Nesta if statements because they're sort of depending on the type checking to sort of catch errors in
01:46:28.639 this case this is I'm not like a 100% convinced of this I maybe 60 or 65%
01:46:34.119 convinced of this but I do think there's something I do think that there's something there I don't know I could be wrong I'm frequently wrong um so one of
01:46:43.239 the side effects here is I think that this has different values for different teams one of the clear benefits of static typing or these kind of runtime
01:46:49.159 checks is it limits the amount of damage you can do in code that you are unform with um because you will hit a wall much
01:46:55.560 faster and unfamiliar code than you will than you would otherwise and if you have like a very big team or if you have a
01:47:01.560 team with a lot of new developers like that can be a big win if you are not under those conditions like that can be
01:47:07.679 that can be a problem um all right so I wanted to leave you with one quote from this book uh the imposition of
01:47:13.679 unnecessary obstacles which is actually book two in a series of really cool science fiction murder mysteries I looked at the platform precarious in any
01:47:20.440 way and wondered again at our human tended to romanticize the imposition of unnecessary obstacles into our lives and
01:47:27.719 I thought if there is any group that romanticizes the imposition of unnecessary obstacles into their life it
01:47:32.760 is developers um please try not to do this like there's a lot of complexity and if
01:47:39.159 the static type is adding complexity that is unnecessary please try not to do it if Dynamic typing is adding
01:47:44.400 complexity that you don't need don't do that but try to figure out what where the necessary and where the unnecessary
01:47:50.199 complexity is and and try not to do the unnecessary NE that's terrible advice on some level um don't do bad things is um
01:47:58.560 but but think about think about think about whether there's a tendency as developers that we think of complexity
01:48:04.280 as a really cool thing to plow through rather than a thing to kind of Route around and and try to be more of a route
01:48:10.880 around uh have more of a route around mindset and that's it um I'm back to the self-promotional slide um this is where
01:48:17.639 you can find me on the internet I think I spelled Mast down correctly this time um I really I genuinely appreciate uh
01:48:25.119 your time coming here at like this sort of awkward time in in a conference I'm sure you're all tired I very much appreciate your participation and your
01:48:32.239 like eagerness to be here um thank you so much I hope you have enjoyed the conference so far and that you enjoy
01:48:37.560 tomorrow and I will see you around
01:48:53.000 the
Explore all talks recorded at RubyConf 2024
+64