Why You Shouldn't Nest Your Code
Summary
TLDR本视频探讨了编程中的“永不嵌套”(never nester)现象,即避免代码过度嵌套以提高可读性。视频中通过实例展示了如何通过提取和反转条件来简化嵌套代码,从而使得代码结构更加清晰。提到了Linux内核风格指南中对嵌套层数的限制,强调了限制嵌套层数有助于编写更优质的代码。
Takeaways
- 🚫 从不嵌套(never nester)是指那些不喜欢在代码中使用多层嵌套的程序员。
- 📈 嵌套代码时,每增加一个内层块(如if语句或循环),就会增加代码的深度。
- 🔢 从不嵌套的程序员通常对代码的嵌套深度感到厌恶,尤其是当嵌套超过三层时。
- 🔄 解决嵌套问题的两种方法是提取(extraction)和反转(inversion)。
- 📂 提取是将函数的一部分抽取到自己的函数中,以减少嵌套。
- ↔️ 反转是通过翻转条件并使用早期返回(early return)来简化代码结构。
- 🔄 应用反转可以创建一个验证门卫部分的代码,它声明了函数的要求,然后是实际功能的核心。
- 📊 在阅读代码时,反转可以使人更容易忽略条件,专注于核心代码。
- 🔍 通过提取和反转,可以将一个大的、处理多个任务的函数重构为多个小的、单一职责的函数。
- 📝 Linux内核风格指南建议,如果代码需要超过三层的缩进,应该重构代码。
- 📈 限制嵌套可以迫使程序员写出更好的代码,因为它们需要考虑如何简化代码结构。
Q & A
什么是“never nester”?
-“never nester”是指那些不喜欢嵌套代码的程序员。他们通常会避免在函数中添加过多的内部代码块,以减少缩进级别。
为什么“never nester”不喜欢嵌套代码?
-“never nester”认为嵌套代码会增加代码的复杂性,使得阅读和理解代码变得更加困难。他们倾向于保持代码的简洁和清晰。
在脚本中提到的“disgust-o-meter”是什么?
-“disgust-o-meter”是一个比喻,用来形象地表示程序员对代码嵌套深度的厌恶程度。嵌套层次越多,这个“厌恶计”就越高。
脚本中提到了两种减少代码嵌套的方法,它们是什么?
-两种减少代码嵌套的方法是提取(Extraction)和反转(Inversion)。提取是将函数的一部分抽取成独立的函数;反转是通过改变条件判断的顺序,使用早期返回(early return)来简化代码结构。
如何在代码中应用提取方法?
-应用提取方法时,可以将循环或条件判断中的内部代码块移动到一个新的函数中,从而减少原有函数的嵌套深度。
反转条件(Inversion)在代码重构中有什么作用?
-反转条件可以帮助程序员将代码的“快乐路径”(即正常执行路径)放在更浅的嵌套级别,而将错误处理和异常路径放在更深层次。这样可以使得核心逻辑更加清晰,易于理解和维护。
脚本中提到的Linux内核风格指南对缩进有什么建议?
-Linux内核风格指南建议,如果代码需要超过三层的缩进,那么应该重构代码。它还通过将制表符设置为8个字符宽来视觉上强制限制缩进。
在脚本中提到的下载文件的代码示例中,主要的处理流程是怎样的?
-下载文件的代码示例主要处理流程包括:从队列中获取待下载的URL,管理当前下载任务,处理下载状态(如待处理、进行中、已完成),以及处理错误和重试逻辑。
脚本中提到的重构前后的代码有什么不同?
-重构前的代码高度嵌套,难以追踪和理解。重构后的代码通过提取和反转方法,将大的函数拆分成了多个小的、单一职责的函数,使得代码结构更加清晰,逻辑更易于理解。
脚本中提到的“happy path”和“unhappy path”分别指什么?
-“happy path”指的是代码的正常执行路径,即没有错误和异常发生的情况。而“unhappy path”则是指错误处理和异常情况的路径。
为什么限制代码嵌套深度有助于编写更好的代码?
-限制代码嵌套深度迫使程序员思考如何简化逻辑,避免复杂的条件判断和深层嵌套。这通常会导致代码更加模块化,每个函数都有明确的职责,从而提高代码的可读性和可维护性。
Outlines
😎 从不嵌套的编程哲学
本段介绍了所谓的“从不嵌套者”(never nester),这是一种编程风格,避免在函数中使用过多的嵌套。作者通过比喻和实例解释了嵌套代码的复杂性,并提出了两种减少嵌套的方法:提取(extraction)和反转(inversion)。提取是将函数的一部分抽离成独立的函数,而反转则是改变条件判断的顺序,使用早期返回(early return)来简化代码结构。
🔍 应用提取和反转重构代码
这段内容继续探讨如何通过提取和反转来重构代码,使其更加清晰易读。作者通过一个下载文件的例子,展示了如何将复杂的嵌套逻辑分解成更小、更易于管理的函数。这个过程不仅提高了代码的可读性,还符合Linux内核风格指南中关于缩进级别的建议。作者强调,限制嵌套层数可以迫使开发者编写更优质的代码。
Mindmap
Keywords
💡从不嵌套
💡厌恶计量器
💡提取
💡条件反转
💡快乐路径
💡代码重构
💡异步下载
💡Linus Torvalds
💡失败队列
💡主线程
Highlights
Never nesters are programmers who dislike deeply nested code.
Linus Torvalds is suspected to be a never nester, as per Linux kernel style guidelines.
Deeply nested code increases cognitive load, making it harder to follow.
A never nester's disgust-o-meter grows with the number of nested tabs.
The maximum nesting level a never nester can handle is three deep.
Two methods to reduce nesting are extraction and inversion.
Extraction involves pulling out part of a function into its own function.
Inversion involves flipping conditions and using early returns to simplify code structure.
Applying extraction and inversion can lead to a validation gatekeeping section in the code.
The happy path of code should be at the main level, while error paths are indented.
Limiting indentation can force programmers to write better, more concise code.
The Linux kernel style guidelines recommend against more than three levels of indentation.
The tab size in the Linux kernel is set to eight characters wide to visually enforce indentation limits.
Breaking down a complex function into smaller, single-responsibility functions improves code clarity.
The main function should outline the high-level logic of the program.
Refactoring a complex, nested function can make it easier to understand and maintain.
Transcripts
I have to admit, I'm a never nester.
I know.
Shocking.
But there are more of us than you think.
Dozens. Even Linus Torvalds is one.
I mean, I haven't asked him, but I'll show you what I mean in a little bit.
You might be wondering, well, what is a never nester.
A never nester never nests their code.
Okay. Not never.
But we do have a disgust-o-meter which grows
uncontrollably as the number of tabs go up.
Nesting code is when you add more inner blocks to a function.
We’ll consider each open brace to be adding one more depth to the function.
So this function is one deep because there's no inner blocks.
And if we add an if statement, we've made it two deep.
If we add a loop, we've now made this function three deep.
And this, my fellow
programmers, is the maximum a never nester can handle.
A never nester doesn't dare to go four deep.
Now the perverse among you might wonder what four deep even looks like.
And while it brings me great pain to do,
I understand that I must show you for science.
Here is four deep.
We've now taken a reasonably readable function and dramatically increased
the amount of conditions your brain must simultaneously hold.
But what can we do about it?
Well, there's two methods you can use to denest: Extraction.
This is where you pull out part of the function into its own function.
And inversion, which is simply flipping conditions
and switching to an early return.
Let's look at extraction first.
We can extract the inner part of the loop into its own function.
Now we can apply inversion.
When you put the happy path of code
within deeper and deeper blocks, it creates a lot of nesting.
Instead, we'll invert that condition and put the unhappy first.
First, we'll flip our if else, by inverting the condition.
Now, since we can return here, we know that the else block isn't
actually needed so we can flatten our else into the main level.
Now, if we hit our unhappy case
condition here, we simply get out of the way.
And then the main part of the code can do its job.
When you have a lot of conditions to check like this,
we can apply inversion over and over again
and we end up with a sort of validation gatekeeping section of the code
which sort of declares the requirements of the function.
And then we have the
crux of the real functionality here.
And you'll notice that the happy path moves
down the function and all of the error paths.
They're indented.
When reading this code, I find I can mentally discard
the condition and focus on the core code versus when it's nested.
I find myself having to hold these ideas in my head.
I'm curious if you’ve experienced the same thing?
Let's look at a larger example.
All right.
Look at this beauty.
Before we go refactoring it,
let me walk you through what's happening.
The goal of this code is to download a bunch of files from the web.
It talks with this download class that we can't alter.
It's an async download.
So when we start the download, you have to call process over and over again.
And each
time it gives you one of these results.
If it returns InProgress, we'll need to keep calling process() more.
On top of that, we want to download multiple files at once in the background.
So we've created a thread that manages all of them.
The way new downloads enter the system is through this append download method,
which puts the requested URLs onto a queue.
The thread then wakes up and grabs the URLs from the queue
and then adds them to this list of current downloads.
Each download is given a state
which is either pending InProgress or Complete.
So in each cycle of the main loop, the thread walks
through each download and checks what it needs to do with it.
If it's pending we start a new download.
If it's complete,
we simply remove it from the list.
If it's InProgress, we call that process method we mentioned earlier
and figure out what's happening with the download.
If it's completed successfully, we mark it as complete
so it can get removed from the list and InProgress means
we do nothing because it's still ongoing.
But things get interesting if we hit an error,
if the connection was okay, but we got an unhappy HTTP response.
We determine whether the air is retriable
If it is, we retry up to three times,
setting the download back to Pending.
Once it's failed plenty, we ejected from our download list
and push it to a failure queue for someone else to deal with.
For connection error, we retry three times.
Then we set the special connection disabled flag and this causes us
to basically give up on every download and clear the list.
Okay, so there's a lot going on here and it's all heavily
nested in this function, which makes it hard to follow.
So let's apply extraction and inversion to flatten
the first two big candidates.
Here are the two big branches of download processing:
Pending and InProgress.
So let's extract these out.
We'll move the pending part to processPending()
and InProgress
to processInProgress().
That's a bit better.
But this in-progress function is still too deep for my liking.
The worst offender is this HTP error section.
So let's move that out as well.
Now we'll keep extracting further in our run function.
We have four major sections of our code.
Where we process incoming requests from the queue;
Where we deal with our current downloads;
where we clear out the InProgress downloads,
and where we wait for the signal that there's new downloads to look at.
So let's do it.
So now
our main function clearly outlines the steps that are happening.
You can see the high level logic I described before.
And if you were to dig into any of these functions,
there are also concise.
At the beginning of this video, I mentioned that Linus
Torvalds is a suspected never nester.
And I say this because in the Linux kernel style guidelines they state
if you need more than three levels of indentation, you're screwed anyway
and should fix your program.
The kernel dudes are always so dramatic.
They visually enforce this
by making the tab size eight characters wide.
This is what eight characters look like with heavy nesting.
Yeah.
I'll admit I'm not that committed to the cause, but I am into limiting indentation.
I believe that constraining
how much you nest forces you to write better code.
If you notice, instead of one large function that handles many things.
We now have small, concise functions that have one responsibility.
What do you think?
5.0 / 5 (0 votes)