Coveros consultants Byron Katz and Matthew Taylor continue their work on the open-source timekeeping project their team is working on. This episode is dealing more specifically with generics in the code. Technology stack: Kotlin, JVM, Intellij, Gradle Open source at https://github.com/7ep/r3z
Byron Katz 0:00
Anyway. So at like 2:30 in the morning, it occurred to me that there was yet another little spot where I could potentially reduce duplication. And this is part of the whole like TDD, Red Green refactor approach acceptance. The way you might learn about it, they'll say like, Oh, you just do a quick test, and it compile or it fails in some way you make it succeed, and then you quickly improve it a little bit with refactoring. And then you move on. But what I found is, oftentimes, the refactor may not even occur to you until much later. And sometimes the refactor is big, it's like, we've got lots of test coverage over everything. But stepping way back and looking at it, you realize that there are these kind of repetitive patterns that have been set up and so a big refactor is suitable. And since you've got the test to cover it, you're operating in total safety the whole time. And so it kind of breaks that simplistic approach, that your TDD, you're creating a test every single time for each new change, because you've already got all that great coverage. So you can just make changes with impunity running the test again, and again, knowing that you're going to be safe. What occurred to me, I was looking at project here. And in fact, let me go ahead and stash my work for just a quick moment. So I can show you where I started. Ah,git status, looking pretty good. Git stash. Okay. Currently, when we're serializing, like, for example, project, project has an ID and a name. If we want to serialize it. We have a little piece of code here, where we set up the key value pairs when serializing. So for example, the string ID, and it's value name and it's value. And when we go look at serialization, it's not, it's not visible here, that serialization code because it's in the abstract class, the parent to this. And really, the only thing you need to do to think about that is that I have stashed common code for for each thing that needs serialization in a separate place. So they all share that code.
Matthew Taylor 2:47
So are you are you saying that in Kotlin, if you do a type annotation, it also it is actually inheriting that class whenever you do colon type?
Byron Katz 2:59
Not necessarily. In this case, I'm inheriting it because the parens at the end indicate that it is actually a class if I didn't, it would be just an interface. In fact, indexable serializable is made, if we go look at that.
Matthew Taylor 3:17
So is every primitive you type annotation actually being used as a Kotlin interface? Like, is it indistinguishable? They're just a very, very common use case.
Byron Katz 3:26
I'm sorry, can you repeat that? Please?
Matthew Taylor 3:28
When you do any type annotation of like a primitive class, are you using a Kotlin interface? And you may not be aware of it, like if you just do colon ent?
Byron Katz 3:40
Oh, yeah, yeah. Well, it, let me let me think about that for a second. If you say like, maybe there's some item here, like val count, type of ent, like that. And it's complaining about it. So whatever. Val count ent equals two. So you're saying this is a of type this? And similarly, it's very similar and maybe identical to say that this class is of type indexable serializable.
Matthew Taylor 4:42
Well, and yeah, I guess importantly, one is a class declaration and one is a variable.
Byron Katz 4:48
Correct, yeah. So yeah, I mean, I can't designate this thing as a type of like an abstract, primitive or something, but I can with a class, this is inheriting from a class. In this case, it can have a combination of multiple interfaces, but can only ever inherit from one class. So for example, I could say, foo and bar. And these aren't abstract classes, because there's no parens at the end. But this one is. The complicating factor is if you inherit from multiple actual classes, the language has to figure out which implementation you might have meant. But if you're inheriting from multiple interfaces, all that is is like, the, the signatures that are allowed. And so if they overlap, it doesn't matter. Because in foo, there might be something that says, like do stuff and similarly in bar and if they're identical, then it's like, all good. Who cares? There's no implementation. But if you've got multiple classes on there, they will have different implementations. And then it's like, I don't know, which one do you want me to do?
Matthew Taylor 6:20
Can you always do multiple interfaces? Or is that just Kotlin.
Byron Katz 6:24
Um, Java also allows multiple interfaces. And one class I believe, C++ allows multiple classes, as I recall, and that makes things very complicated. I think it's like the order that you bring them in determines the order in like, which one gets priority. It gets pretty complex, and painful. And so in Java and Kotlin, they wanted not to do that. And also C sharp, only one class, I believe, but multiple interfaces. If we go look at this, it's an abstract class. And the reason it's an abstract class is it can't be an interface. And one of the things that it inherits is an abstract class serializable. It takes in one interface in one abstract class. The only reason that I combined it here, the language just doesn't really provide the necessary mechanisms, when I'm using generics to have like multiple things. So okay, so when it comes to the concept of generics, what you have to understand is, it's not some, like big, essential, magical thing that applies throughout. It's a consequence of the particular ways that Java and Kotlin were implemented. In, in some other languages that aren't as strongly typed, generics is just a piece of cake. You know, you've you've dealt with Python quite a bit. So oftentimes, you you're getting you're not you're getting the benefits of, of generics, without having to really think about it because there's no strong type to consider. Like in Python, you might add two numbers. This is Python. And you, I can't remember the exact syntax, but like, maybe you do, like, what is it like function, Python, or function add. And it takes in an A and a B?
Matthew Taylor 8:53
Something like that.
Byron Katz 8:55
And so you would return A plus B. And it's all good, because Python doesn't require the strong typing. So it's kind of just it just works, you see? But in languages like this, that require strong typing, I need to specify that for example, this thing is an ent and this thing is an ent and then it kind of works there. But then, in this case, it only works for ent. So wouldn't it be nicer if I could, if I want to do this for float, then I have to do this float and float. Now I can add. I can do add two and two, and I can add 2.0 and 2.0. And I don't have to think about it, it just while figuring out which method to use it says, Oh, these are floats. I'll go to this one, you see? But generics in this language are to say that it's a bit broader. And yet it's still strongly typed. So maybe...
Matthew Taylor 10:18
My lawyers require me to note that Python is strongly typed, but but not statically typed. Which might be relevant, especially since they've also they have the Kotlin style annotations now.
Byron Katz 10:33
Yeah, I'm just kind of pulling this out of the air. I mean, the essential idea that I'm...
Matthew Taylor 10:42
There you go on.
Byron Katz 10:43
Well, maybe it's JavaScript, maybe just for the sake of argument, let's just say that whatever language is, this is some language that doesn't care about type as much okay. So it's not Python, it's not JavaScript, it's just whatever. And, and in there, we would say function, add A and B and just return A plus B. But in Kotlin, we have to be specific.
Matthew Taylor 11:27
So I'm trying to think how much of this you get for free in other languages, and like whether it's being done kind of without realizing it all the time? Because basically, with Python, even when you don't use the annotations, it's strongly but implicitly, strongly typed, if that makes sense. Like...
Byron Katz 11:42
Yeah, yeah, but I get that. But like, what if you were like looping through something like loop through this 1, 2, 3, 4, 4.0 or maybe there's some function that's like, function do stuff. And in here, it's like, A is equal to B is equal to 3.0. Add A and B.
Matthew Taylor 12:22
So anytime you have multiple functions to handle different type input, like a consequence of generics?
Byron Katz 12:33
The concept of generics in Java and Kotlin, or at least the Kotlin, that runs on the JVM is is purely just a consequence of the ugliness, the imperfection of the stuff that they have to do to allow this broader use.
Matthew Taylor 12:54
So I'm trying to think, what would happen if he did that in Python? I'm pretty sure what would happen is it would throw a compilation like a runtime compilation. It would say can't add ent to float can't can't implicitly convert ent to float, or something along those lines.
Byron Katz 13:12
Is it deaf? I can't remember. Is it deaf? Okay. So add and we do A and B, is that right?
Matthew Taylor 13:21
Yeah, that'll work colon.
Byron Katz 13:23
And then we would say, just A plus B, or do I need return at the front?
Matthew Taylor 13:29
Probably, to be safe.
Byron Katz 13:34
Okay, so now we say add two and three, and we get five. We say add 2.0 and 3.0. And we get 5.0. Now, if we add 2.0 and three, it cast three to a 3.0 and adds them. way
Matthew Taylor 13:51
The other way around.
Byron Katz 13:53
No, I think it casts because it ent is 5.0.
Matthew Taylor 13:57
Just Just to be careful, try two and 3.0.
Byron Katz 14:03
5.0. So it's doing Yeah, I think you're right. I mean, it's strongly typed, it knows what it's doing. It has certain rules it's following.
Matthew Taylor 14:12
It just assumes that you because three is a valid float input. It just treats it as a float if one is a float, yeah. Which is kind of an assumption. Okay, there's a different thing you can try. That might get a slope you want. Make a function of like, add, oh, well do strings, add the number and a string with what you have.
Byron Katz 14:36
But I think that would I think that would work too, because it'll take the number and take it up to a string. So well, let's try Yeah, so what do we can do like add? Oh, we're just gonna...
Matthew Taylor 14:47
No no like, use the existing add and make um one of the numbers quoted.
Byron Katz 14:52
Okay, so we'll say one and two.
Matthew Taylor 14:56
Yeah. That should yell at us. Because one cannot reasonably be interpreted as an ent, whereas one can reasonably be interpreted as the float one. Yeah. Right.
Byron Katz 15:09
So I mean, at the end of the day, generics are about a way of saying this thing does accept a variety of types, even broader, perhaps than was originally envisioned by the language that you determine. You determine what makes sense. So for example, I might create some add, well, I won't be able to do it, as well as Python. But let me jump back to Kotlin here. Now, Kotlin is not going to let you just, well, let's find out there's actually a repple. So let me just chop this for a second. Let's go to tools Kotlin, Kotlin, repple, I haven't used this too much. But we can just try it out. Two plus two, run. Oh it's just Ctrl. Enter to execute. Two plus two Ctrl, enter, four, two plus three Ctrl, enter five, all right. Two plus 3.0. It, it converts that too. But now, if I created a new function add, and I say A and B, that returns a I'll call it add thing. So it definitely doesn't conflict with any current stuff. Add thing A and B returns a what?
Matthew Taylor 17:01
Okay, I see. So so you would, let's say you make it ent here. That's well and good. The built in add similar to pythons built in add and the implicit behavior of the plus function has already implemented a generic hasn't it?
Byron Katz 17:18
Yeah, of a kind.
Matthew Taylor 17:21
Let's say ent here instead of a generic. And of course, it'll do all the things actually, are you allowed to give it no type annotation?
Byron Katz 17:29
You can say any? I don't hear any. Okay, so well, I'll give it a try. Any, any any in return for me? I literally have no idea what's gonna happen here. But we'll just give it a try. We'll return A plus B. And we'll run that. It might not allow me to define things in here. Oh, oh, it's actually yelling at me, because the operator dot plus that we're using here, it doesn't know is trying to strongly type it. It's like, okay, fine, I'll do any and any and any. But I don't know how to give you any in any and any as return. So let's do it. Like if I say add thing, a dot any, and b as an N A and returns a string? Can we say there A plus B?
Matthew Taylor 18:50
Now, one thing that's interesting about Python and Collins behavior earlier is that the plus function was what did it and they weren't going to type anything until addition forced it to. Yeah. So you don't just get things changing type for no reason. You have to like, do something to them, which, which is authorized to change their type.
Byron Katz 19:15
Right? There's it's quite a bit stricter in Kotlin, in general, than Python is. Not to denigrate Python at all. I like Python. But from the get go in Kotlin and Java, you have to specify things. And because of that.
Matthew Taylor 19:34
It's programming equivalent to a hippie commune with no laws, okay.
Byron Katz 19:42
So yeah, so I mean, because of that, you have to use generics and generics, it should be called something to make it clearer that it's very specific to the particulars of, of what they were creating. I mean, I don't want to get down into any of the weeds. I'm not sure I even can do it that well about type erasure, or anything like that. But basically, this is the circumstance we're in, we want to use more than what it would ordinarily give us. So to do that, we use types ah generics, generics. So we would say, function add thing. And we have A and B, and it returns something. What we could do here, though, in Kotlin, is we can say that, when we're dealing with this A, there's stuff that we're guaranteed to be able to do with it, we're guaranteed to be able to do stuff with this. And we can specify what this thing should be, based on what what comes in here. That's what generics is really about. So I can have a little bit of stuff in here where I say like, for example, oops, I could say the type of A, let's see, type A type B, result type. Normally, these are just indicated as a single letter, by the way, just by convention, but we can also use long, full words if we wish. And so doing it that way, this will be result type. And this will be type A, and this will be type B. And so we then fill these things out, when we're calling this method. And as long as this the the code that we write for this kind of works, like let's say, let's say that in type A. The question is, what does type A allow us? What kind of methods does it allow? So if we're adding to things that might mean that we could say A.add. And it takes a type of type B. Are you there?
Matthew Taylor 23:05
I'm here.
Byron Katz 23:06
Okay. So this this add piece right here, in the interface, in this type, this is this, this type A would probably be some interface. And it would, it would have an add function in it. That takes in type B things.
Matthew Taylor 23:31
Yeah, I mean, that's the simpler way of doing it that an add takes two things is each type having a if you want to add to me function
Byron Katz 23:42
Yeah, and, and maybe when i, this ad here always returns a string. So for example, I, let's see if this kind of works, I could say interface type A, it has a function add that takes in something of type B, and returns a string we'll say. So there's also a class thing that has as an interface. Let's actually make this a bit clearer. This isn't called type A this would be like adding, that's the interface or adder. So this is a thing that has an adder interface. Let's capitalize it to be appropriate. And in this thing I am required to implement add. So that means that whatever I do to implement it, A, since it is of type A, and the type A that we're using is adder when we call add thing, we're going to pass in a thing, or an instance of thing. And other thing goes here. And class, other thing we'll call it takes a stuff, it can get a little complicated.
Matthew Taylor 26:24
Crystal clear.
Byron Katz 26:29
So uh part of understanding, I mean, part of understanding generics like this is I should probably simplify it down because I'm using a whole bunch of types. And if I do that, it just makes it way more complicated than needs to be. So I'm going to take it way down a notch. And I'm going to say, I just take one thing. You know what, maybe another way to do this is just to show you what I've actually done. So abstract data access, really all I want is that, when different things are being operated on in the database, I can do so in a strongly typed way. And something a bit more fluid. I wouldn't necessarily need to have to use all these like Ts and Rs and things because it would just I just wouldn't need specified it would figure it out on the fly. But in Kotlin, I have to specify certain things. I want some kind of interface, so that for whatever it is a project, an employee, a user, I'm able to act on it, and I'm able to read.
And so the T here will will get replaced by project or employee. And then I also specify when operating on this thing, what the result will be. So because when I call act on, I'm giving it enough information to know what I'm popping in and what I want back, it fills these in appropriately. So it, it basically creates for me, oh, actually, just for your own information. What it's doing is it's looking at the code because this all happens at compile time. So it's looking at the code and saying, Okay, well what kind of things are going to be used? Well, it looks like he's, if I look for usages. Find a kind of simpler one here. This one looks good.
Yeah, this one, this one's pretty simple. Read, if I go look at that. The generic version of it is I'm taking a set of some type, and it comes up with some response.
I have to specify that maybe in Python, I could just say it's a set. And maybe in Python, I don't need to specify that it returns something or other. It's just kind of it figures it out. But in Kotlin, I have to say what exactly it takes in what exactly it returns. Due to that structure, I am forced to use this mechanism called generics to specify those things. While it's compiling it sees that I am passing in a user, and I am returning the R actually becomes a set of users. So this fills it in with this being user and the result being set of users. And it actually builds that code. When you compile, if you were to decompile it, you would find a read method that had been built, that took in a set of users that returned a set of users.
It's not happening dynamically, exactly. It happens at compile time. And then it just kind of statically sits there as part of the compiled code afterwards. So if you call this from a whole bunch of places, with all different types, you're actually ending up creating a lot of extra code that gets built into the binary classes.
Matthew Taylor 31:14
Yeah, I think this this example got a little too complicated for me.
Byron Katz 31:18
Um, ok. It really helps when you start to make these yourself. And the place that you're typically at when you start to need to make these yourself is when, like, Okay, um, I used to have this in the code over and over again.
If I go to get here, actually, let me go to commit just to double check. I'm not doing anything nutty - unversioned files, D serializable, serialization keys. Oh, I kind of do want that one second while I fix this. Get stash pop. And then get stash dash A. Ugh that's actually not what I meant to do. Probably really mucked myself up here. Let's see. I should only have a few. It didn't finish. So hopefully that's still good. Let's, let's see. Git stash dash question mark. I really just want to include untracked. Okay, git stash dash you. Okay. Hopefully. Hopefully, that worked. Okay, yeah. And if we go to git, I can scroll back just a little bit here to here. Let's check that one out. And now let's go to pure memory database. Okay. So back back when just a few days ago, in order to work with reading the users acting on the users, I had to actually have these like this. Now, they still had generics at the time, because when I was acting on users or reading users, I needed to strongly type what it returned. I think in other languages that have been more flexible, I don't have to explicitly specify what it returns. But looking at just this piece, kind of ignoring some of what we've seen before, if you can clear out clutter, act on users will simply take any action that begins with a concurrent user and returns something. And it, if we go find the usages of this, let's find the kind of simpler one. In this case, I am returning a single user. So this is running act on users. And I am returning one single user. So by calling it this way, it actually ends up creating code in the built binary that looks like this.
Does that make sense? It builds it builds this for that particular situation. But let's undo that now. And in this case, I am, that's also just one user. This is also a single user. And this one's a set of users. So in this case, when it compiles this code, it's going to replace this with set of user. And this part goes away. So it basically ends up building two acts act on users, one that returns a single user and the one that returns a set of user.
Matthew Taylor 36:33
It's to go back to the generic form or the with R form, I don't even know what to call that. Okay, so I think I get that you have a standard for something that is a rule in and out like, um what am I thinking? I guess I see what the R is doing. Sometimes I've seen in other languages, or examples that have just like a little bracketed R on the left. But given that there is something that can be substituted at, like multiple points, when when generating the different functions. Makes a bit more sense. Like, it would have to be both in the input and output for it to make any sense. And I see that sort of happening.
Byron Katz 37:22
Well, okay, in this case, it's smart enough, because it sees what I'm returning here. If I didn't, if if it wasn't smart enough, what I would have to do is say, you know, set of user. I would have to pass that in to say, hey, build this one with this as the type. But I don't need to because as you can see, remove explicit type arguments, because it's already figured out what what I'm doing here.
Matthew Taylor 37:54
It's confusing me, the bracket syntax is coming up at all, when I thought and hoped that Kotlin primarily used colon syntax for any type typing. But I can do that liberally. And...
Byron Katz 38:10
So yeah, this is like a slightly different use of it of types where you're specifying This is what I want. And, and understand the reason why it's so painful and off the ordinary, of like using colons, and all that is because they really are building this stuff, each time during compilation, and so they don't want it to get mixed up with the real typing. They want, honestly, they just want to figure out a way to kind of stash it. So it's obviously something special that they can look at during compilation. I mean, if that was like, before even running the method, you say, haha, and then LOL at the end, and you put in this type here, it was it would be able to do the, you know, string replacement that it needs to do to take this and make it the return type. So whatever what it's doing, I could put it in brackets here to specify what I'm doing. But it's smart enough that I don't have to do that. But the only reason it's using these is because it just it needs to be somewhere that it can recognize it.
Matthew Taylor 39:18
If you didn't bracket R, then it would just look for a class R and tell you to import one or something.
Byron Katz 39:25
Yeah, if I wasn't if if it wasn't possible to know. Like if I wasn't using the R and inside this method as a parameter, like for example, I know I want to return an R. But instead this is just going to always return an any. There's no it can't figure out anymore from looking at the parameters what R should be so I would have to specify what the R would be.
Matthew Taylor 39:50
And did we say that it could be any letter or word? Not R, R is just...
Byron Katz 39:55
Yeah R is just a stand-in.
Matthew Taylor 39:58
I guess maybe it would be helpful as a style thing to say exactly what like set of classes where we're describing or set of types?
Byron Katz 40:06
Yeah, actually very good point. And that means that you can do that you can say, R is in any.
Matthew Taylor 40:16
Oh, I mean, for the name of R, but also typing are is cool.
Byron Katz 40:20
Oh, yeah, just by convention is shorter. And what you would typically do is, you would typically say, like, I think you would actually say, like, R there this is the return type. And in fact, that's what I had planned to do.
Matthew Taylor 40:39
Brian is like criteria for what good implementations are like, it's probably going to be a set of something, for instance.
Byron Katz 40:52
Well, but yeah, like, coming back to a second ago, by specifying here what this kind of thing is allowed to be, then you kind of construct it to say like, it can't be just absolutely anything, it has to be like, a set of something. Or it has to meet the interface of foo foo. If you know, for example, if we're taking an R and we're also say, we're taking also a T and T has to be of type foo, foo. Then whatever foo foo has as its interface, like it allows a do foo, you know, that you'll be able to do foo on the T.
Matthew Taylor 41:43
100% with you.
Byron Katz 41:44
If I mean, the one way to think about interfaces, a simple way is that they enable certain methods, right? Like, you can have an interface that does like a do foo and a do bar or whatever. And so given that T is of type Foo Foo, you can kind of say, like, well, in here, I'm going to do a do fu it's guaranteed to be allowable, because I know that T is of type Foo Foo?
Matthew Taylor 42:17
Sure, sure. I think I get that.
Byron Katz 42:22
So now let me go back to where we are currently, let's go to the terminal, get reset.
Matthew Taylor 42:32
I think for a future session, I need to have a goal in mind that we know can only be achieved through the use of the generic, make it like from the ground up and us baby toddler project, because I think it would help to know exactly what and why the thing being done to the return is doing
Byron Katz 42:54
Well, the reason why I was doing all that is because I don't have to now I don't have to have all that code in here anymore. I could just implement this
Matthew Taylor 43:02
Inner class, God.
Byron Katz 43:04
Yeah, it's an inner class, because an inner class allows it to have access to the surrounding variables. Specifically, I need access to the employees, or the projects or the users. I wouldn't have access without that. But so I'm specifying this one when I when I build this, hey, Kotlin when you build this thing, build it with type employee for the T and let's go look at it. And so the T is right there and see how it highlighted t all over. That's all getting getting replaced with the word like employee or whatever. It's literally getting replaced, like it's literally doing a string search and replace during compilation and building a whole new class for each one.
Matthew Taylor 44:03
I think I'm with you. I just don't like fully understand it because I didn't have a need and then meet that need.
Byron Katz 44:09
Yeah, right. Once you start seeing a lot of duplication, and there's no way to reduce it without having some kind of overlapping or some some way to avoid the types killing you, you start talking about generics, and...
Matthew Taylor 44:25
I think, maybe a good exercise and I could even try to help build this would be like a scenario in which we have a very comprehensive add function with a ton of repetition. And then we work through making it like five lines.
Byron Katz 44:40
Yeah. Yeah. This is a conversation with Josh Bloch, who is a very well regarded developer on Java. He wrote what did he write, Effective Java Programming and Effective Java. I mean, it's just a really famous book. But he talks about generics in here. And actually, I think he's a pretty reasonable fellow. And he talks about, it does add a lot of complexity, you only have to peruse Angelica Langers 513-page Java Generic Fact to appreciate this. So I wish we had been able to simplify the design. It's complicated, because there were little, you know, sharp corners to this thing. And
Matthew Taylor 45:39
I mean, I don't mind the repetition that they're solving. It's very explicit and clear, when you look at things like it's fine. And so I'd be in the camp of like, I'm still here for explicating this and not having your program written through you through arcane druidism. But I can see where it's useful.
Byron Katz 46:08
Yeah, that's all it really is. It's just all these things are all about just reducing duplication, if you can, if you're running the same thing again. And again, not only do you have to go back there to maintain it all, but you might be adding bugs in one and not the other. And when you want to go back to correct things, you have to correct it in like 100 places rather than one.
Matthew Taylor 46:27
Hum yeah.
Byron Katz 46:31
That's really all it's about just reducing lines of code they have to duplicate. I mean, it's not about typing. If it were just about typing out code, I wouldn't mind but being a human, if I have to type it again. And again, for all those places, I make mistakes. And that means bad. And by the way, part of what I'm doing here is I'm also in the midst of building something. Let's see here, it gets dash pop. Good. So I am I am building a thing. For the deserialization part. Where is it? Project, I'm starting with project. Oh, let me just open up project. So I used to have just deserialize, kind of sitting out there. And it was like, it's totally custom for each class, it has to be there to be able to be serialized. But there's nothing that really kind of leads you to where you like, what you need to build and how and so on. And it was it had some real rough edges to it. So where I currently am is trying to create a new interface for the deserialization side. So that it's kind of consistent and known and stronger typed. And we can all explain this in more detail later on. But this is definitely using generics and pretty kind of advanced generics because I'm I'm actually having to put generics on enums.
Matthew Taylor 48:33
Are you talking? Still?
Byron Katz 48:36
Yeah, I've been talking for the last five minutes. Oh, did you leave or?
Matthew Taylor 48:42
It disconnected for a while I just couldn't hear you. And then it like fully booted me. Surprised it didn't make a sound on your end or something.
Byron Katz 48:51
Oh I'm sorry. Yeah, it didn't ding or anything.
Matthew Taylor 48:54
My internet, like flickered weaker, and it first degraded my call and then like booted me.
Byron Katz 49:01
Ah well, I did make a recording of this. So you can always review what I said over the last five minutes.
Matthew Taylor 49:08
I believe you that it was high quality.
Byron Katz 49:13
To summarize, I am in the midst of building something that uses generics quite heavily. And it's it's a pretty sophisticated piece. But I'm having to I'm in the midst of building new test for it. Let's see where our database is. These database serialization tests, I think, let's see here um database. Oh, disk persistence tests. So the new test that I'm building is right, right here. And we are testing out this deserializer. Deserializer new serializer new. So this this is pretty advanced generic. So if you can get through this.
Matthew Taylor 50:17
Now of all things to be generic, serialization and deserialization make a lot of sense to me.
Byron Katz 50:23
Yeah. Right. So um like, when are doing it? It's like, huh?
Matthew Taylor 50:33
Not flat map again.
Byron Katz 50:34
Yes, flat map. We're what, what we used to have was, let's go to employee we had, here's the key, and then here's the value that goes to it. And then when we deserialize. It I have to say groups, one, groups three, yuck. What I would much rather say is something like, like, instead of this being just called like, groups, it could be called like, the map. And then this is like the map. And we pass in like ID. And this would be like an enum. It wouldn't even be a string, it'd be an enum. So it's like, super strongly typed. That's where I kind of want to be because having to say groups three, is begging for bugs.
Matthew Taylor 51:26
Hum yeah.
Byron Katz 51:31
And having to specify this as a string here, I really want to do that. And, and I really want to say, allowed key or yeah, allowed keys. Enum allowed keys ID name. I use wishful thinking here. I'm doing TDD. So I'm writing what like what I wish it was like first, and then kind of reverse engineering to make this actually work.
Matthew Taylor 52:08
I still like good old strings, kind of conflicted on this one.
Byron Katz 52:14
Well, what happens when I accidentally, you know, spaz out and type it like that?
Matthew Taylor 52:22
Yeah, that's fair.
Byron Katz 52:27
By having an enums. There's no question about it. And like, if I try putting in an enum that's not valid, like ID one, it'll be like, sorry, that doesn't exist. But I put in ID exclamation mark here. It's good. It works fine. Enums are awesome. Because strings can just be anything and enums can only be a very small set. And so what you have to consider about what could go wrong? What could go wrong, becomes much less. That's the beauty of enums. Does that make sense?
Matthew Taylor 53:07
Yeah, I, you know, enums are good. Enums are good.
Byron Katz 53:13
So now going back. This is not their project is where I wrote my wishful thinking, this is the legit wishful thinking that happened earlier this morning, where I wrote it exactly the way I wished it was. And then I started to actually implement it. That's why it's not yelling, here, because I really did create an enum that has an interface of serialization keys. And, and I'm in the midst of changing the deserializer. So it does it that way. And in here, we get that, that list of items that matched and then we check to make sure it's an even number. And then we started looping through taking the key than the value of the key than the value and passing that back as a map. So that over here, I can just say entries with this enum. But to make sure that we're doing it right, I was creating some tests for deserializer new because obviously, it's getting a bit intricate, and I don't have anything that tested directly although it is tested indirectly by any code that goes through deserialization, but I wanted to kind of hunker down on this one in particular since I'm having to go through this madness
Matthew Taylor 54:38
That's a weird check there.
Byron Katz 54:41
Well...
Matthew Taylor 54:42
Also didn't we established you needed to use require usually?
Byron Katz 54:49
Require is when it's something coming in as a parameter. Okay, by convention require will throw illegal argument exception check throws illegal state exception.
Matthew Taylor 54:59
Okay, I thought at one point we were using check wrong, like most of the time.
Byron Katz 55:05
Yeah, I actually went back and made corrections to that mostly. And this way, I don't have to do any kind of check later on. And here to confirm that I'm dealing with an even number I just checked. This is the state that it must be otherwise. Well, how can this How can it be? Not even if I'm, if I'm doing this, it must always be even otherwise throw an exception. And just as a general rule, being very strict about that every step of the way while building a system means way fewer bugs later on, when I tried adding this kind of stuff to a system that already had tons of problems (healthcare.gov) they were yelling at me because it was throwing exceptions everywhere because they were just using workarounds everywhere to kind of get it working. Hunky dory. Alright, I'm gonna stop the recording.
Link to full video in YouTube: https://youtu.be/FryXTzzm7ws