مرور مرور فصل (و دمو)0:07
As I mentioned, symmetry is one of those practices that will continue to evolve as you continue programming. That's because symmetry can change from code base to code base. This may be due to opinions by other programmers on your team or simply by how that code base is written. It also changes over time. What feels symmetrical to you today may not a year from now. Practicing symmetry really hones your craft.
Introducing Symmetry Example0:27
What feels symmetrical to you today may not a year from now. Practicing symmetry really hones your craft. So let's take a look at one more example, one that allows us to focus strictly on practicing symmetry. The snippet of code is taken from the O Dear application in uptime monitoring service, specifically the Check class. And we're gonna focus on needsToRun, which basically determines if a check needs to run. And if we scroll through the code, it feels pretty nice in looking at it.
And if we scroll through the code, it feels pretty nice in looking at it. We see that it's well formatted. It doesn't have any dead code. Any of the nesting is top level. There are no big blocks, there are no comments. It seems to have reasonable returns, but let's give it a pass for symmetry. Scrolling through the code one more time and checking for symmetry.
Scrolling through the code one more time and checking for symmetry. We can spot a few areas that feel asymmetrical. Everything seems to be a nice method call, but right away this specific check seems to jump out at me. Much like the example by Kent Beck. This feels pretty low level. Once we spot that, we start to see some other asymmetrical areas. Now while I've mentioned three different types of symmetry,
to see some other asymmetrical areas. Now while I've mentioned three different types of symmetry, you don't necessarily need to run through these in order. You should start by refactoring what feels the most asymmetrical to you. Once you start practicing symmetry, it creates a positive feedback loop, making it easier to see other asymmetrical areas. So while you're welcome to check for syntactic symmetry, first, feel free to jump around to any asymmetrical piece.
Refactoring Syntactic Symmetry2:08
So while you're welcome to check for syntactic symmetry, first, feel free to jump around to any asymmetrical piece of code getting back into the code. Let's start with this line. Similar to this conditional, I feel most programmers would agree that these two are the most asymmetrical of the bunch and both seem to lack syntactic symmetry. In this case, we negate the conditional. We don't seem to do that anywhere lower in the code.
In this case, we negate the conditional. We don't seem to do that anywhere lower in the code. It's only these two first checks. In addition, this is direct property access where everything else seems to be a method call. And personally I find when you start to negate these stateful properties, it just seems to be a bit brain bendy. So I'd like to remove some of that complexity by making it a method simply called disabled.
So I'd like to remove some of that complexity by making it a method simply called disabled. This not only aligns the code by making it a method call, but I feel it reads much more human 'cause. I don't have to negate a bullyingFlag. I can just see that if it's disabled return false. Alright, let's move on to this compound conditional. Again, it lacks syntactic symmetry. We not only have a low level check here, we also have a bit of a nested call.
We not only have a low level check here, we also have a bit of a nested coal and a compound conditional where everything else is a single conditional. Because of that, I would actually treat those like a big block of code. So I'll ask the two questions. Can it be done in a more native way? Well, the answer is of course no, because this is a very specific check to this application.
Well, the answer is of course no, because this is a very specific check to this application. So does it belong at this level? As we mentioned, this is a pretty low level check. It doesn't seem to be abstracted the way the others are. So I would say, no, it doesn't belong at this level. Let's go ahead and make one of those temporary comments to say what this code is actually doing. It seems to be determining if it's an uptime check and if the last one failed.
It seems to be determining if it's an uptime check and if the last one failed. If so, return true to check it again. So it seems like it's checking if it should retry an uptime check based on the surrounding code. That's actually a pretty good name for this method. So I'm just gonna go ahead and use it. We'll say shouldRetryUptimeCheck. Great. Let's get rid of that temporary comment now.
uptime check. Great. Let's get rid of that temporary comment now to finish up with syntactic symmetry, there's one last bit I'd like to focus on. While everything syntactically is an if statement, what I'm noticing is that the first two return false, these next few return true and then this returns false again. Again, technically all of these are if statements, but as we've learned in nested code,
Again, technically all of these are if statements, but as we've learned in nested code, these are actually guard clauses. While this is an early return and down here we have another guard clause as a human, I'd like to read all of the guard clauses first and then all of the things that can return true. So I'm gonna move this block up with the other guard clauses to give it a bit more syntactic symmetry. Alright, there may be more we can do.
Improving Semantic Naming5:21
to give it a bit more syntactic symmetry. Alright, there may be more we can do with syntactic symmetry, but I'm gonna leave it here for now and move on to semantic symmetry. This deals mostly with the naming of things. I wanna make sure everything reads well. Starting with the method we have needsToRun. Looking at some of these other method names though we see that they're much more verbose and while that's good, they might be overly verbose.
that they're much more verbose and while that's good, they might be overly verbose. Are they leveraging as much as they can from the surrounding context? What I'm noticing in these more verbose names is they seem to have words that repeat some of the surrounding context. We see check within the Check class. We see these words like has or should for what are already bullying methods and they don't seem to be used consistently.
or should for what are already bullying methods and they don't seem to be used consistently. This is disabled, not is disabled, it's needs to run, not does need to run. So while I like these verbose names, I think we could drop a few words to tighten them up a bit and make them feel more symmetrical with some of these original names. So I'll change this to be activeSubscription or on genericTrial.
So I'll change this to be active subscription or on generic trial. Disabled seems fine here. Instead a check has already been run or is running we could say already been scheduled or is running. I think we could take this one just a bit farther relative to itself. It's also asymmetrical. We have this already been scheduled.
It's also asymmetrical. We have this already been scheduled or is running the tenses don't seem to line up between the two. We also have this extra kind of filler word that we've been removing. So let's just say already scheduled or already running. Now we have some symmetry in the wording. We'll drop this extra should here.
Now we have some symmetry in the wording. We'll drop this extra should here and just simply call it retry uptime check. Moving on to these final ones. Let's get rid of some of those booing filler words. So I'll get rid of has here and we'll get rid of has here as well. Now the words latest and previous feel pretty symmetrical to me. This is where naming can get a little subjective.
and previous feel pretty symmetrical to me. This is where naming can get a little subjective. To ensure that this is symmetrical, you would have to compare it to the broader code base or get the opinions of other developers on the team. Again, what matters most is that it reads well, but also that it's consistent with the other naming used in the application. What I find a little asymmetrical is that this returns a bullyingValue,
What I find a little asymmetrical is that this returns a boolean value, whereas this returns an object. What this has allowed me to stumble upon is because this returns an object, we actually have a relatively compound check here. It's also the last conditional that isn't a simple method call. So what is this code really doing? Well, if latestRun is null, to me that would mean
So what is this code really doing? Well, if latestRun is null, to me that would mean that the check hasn't been run before, in which case we should run it. So kind of like disabled, I feel like this condition could be a bit more human readable. We could say something like this firstRun. Now we have another word that gets into the naming of previous versus latest versus last versus first.
Now we have another word that gets into the naming of previous versus latest versus last versus first. So maybe I'd like to use something else here. This also feels a bit technical. It's not relaying as much as I want it to. Maybe something like initial better relays. What I'm really trying to determine here that is, if this is the initial run, then go ahead and check it. So I think we're getting pretty close here and as we've seen, it's becoming easier
Applying Systemic Symmetry9:10
So I think we're getting pretty close here and as we've seen, it's becoming easier and easier to refactor as the code feels more symmetrical. There's one last pass I want to give it for systemic symmetry. So there are two main areas that still feel a little asymmetrical. This first conditional feels pretty asymmetric actually on two levels. First, it's the last one
actually on two levels. First, it's the last one that syntactically inverts the condition. Second, this seems to reach pretty deep into the underlying objects. Everything else is a top level method on this class. Looking at what this actually does, I don't feel like you should even need to determine if it checks should be run if you don't even have an active subscription.
to determine if it checks should be run if you don't even have an active subscription. So while part of me understands how this ended up here, the other part really feels like this check should belong at the same level that this method is called. So I'll optimistically remove this or that refactor that really just leaves the final call. This is the only, this call too is a bit chained. In addition, it's the only method that takes an argument.
This is the only, this call too is a bit chained. In addition, it's the only method that takes an argument. Since all of these calls are internal to this class, I feel like we could refactor this into a single method. Again, one that better communicates at a human level what this code actually does. So treating it like a big block. Let's just leave a temporary comment. It seems to determine if a check is ready to be rerun. So leveraging the surrounding context,
It seems to determine if a check is ready to be rerun. So leveraging the surrounding context, we could say something like ready to rerun, but rerun doesn't feel quite right. We're not rerunning a previous check, we're running a brand new one. So we could say something like Recheck that feels a little better, but we actually might have lost some context in using the word ready.
but we actually might have lost some context in using the word ready. Other methods seem to use the word time, so trying that out, we could say time to recheck. That feels pretty nice. And in reviewing the final version, this feels wonderfully symmetrical. We have all of our guard clauses at the top. Everything's a nicely named single method call with no arguments, which relay at a human level.
Symmetry Takeaways11:32
Everything's a nicely named single method call with no arguments, which relay at a human level what the underlying code is doing. So that's symmetry. At times it can get a little subjective. That's okay. You have to remember, we're writing code for humans that are familiar with this code base. So long as the code's written the same way here as it is elsewhere in the application, then it has symmetry.
