案例研究js图片库代码_更干净的代码案例研究

案例研究js图片库代码

I recently had a situation at work wherein a coworker tried to modify a JavaScript function that I had written and ended up introducing some bugs. In reviewing their code, it seemed that their main issue was in not fully understanding what the function was doing. I do, however, believe that it was my fault because the function was — frankly — poorly written to begin with.

最近,我遇到一种情况,其中的一个同事试图修改我编写JavaScript函数并最终引入了一些错误。 在审查他们的代码时,他们的主要问题似乎是不完全了解该函数的功能。 但是,我确实相信这是我的错,因为坦率地说,该函数最初的编写不正确。

Sometimes we have deadlines, and, in order to meet them, we may leave things a mess. I had plans to revisit the code but other tasks took priority. Now that the function was back knocking on my door I saw an opportunity to fix it. Often when we share our code with the world, we share our most meticulously maintained material. But that is not the reality of business all the time. At the end of the day, the product and the customers that use it are the priority and when it comes to deadlines vs perfectly clean code, the deadline wins. However, when we get the chance to go back and clean up after ourselves we should take those opportunities because it’s important that we balance production with our capacity to continue producing.

有时我们有最后期限,为了满足他们,我们可能会把事情弄得一团糟。 我本来打算重新审阅代码,但其他任务优先。 现在,该功能重新敲响了我的门,我看到了对其进行修复的机会。 通常,当我们与世界分享我们的代码时,我们会分享我们精心维护的材料。 但这并不是一直存在的业务。 归根结底,产品和使用该产品的客户是当务之急,当涉及到最后期限与完全干净的代码时,最后期限将获胜。 但是,当我们有机会自己回去清理后,我们应该抓住这些机会,因为在生产与继续生产的能力之间取得平衡非常重要。

I’m going to attempt to remedy the diseased function in steps in order to give you an example of how I go through the process of improving code.

我将尝试逐步纠正有病的功能,以便为您提供一个示例,说明我如何经历改进代码的过程。

原始代码 (The original code)

Let’s now look at the original function that gave my fellow developer problems:

现在让我们看一下给我的开发人员带来麻烦的原始功能:

function valid(field, visibleField) {
var state = {
saved: true,
requirements: {
Description: {
required: true,
maxlength: 150
},
DueDate: {
date: true
},
PriorityID: {},
TypeID: {}
}
};

if (!state.requirements[field.name]) {
return true;
}

var errorField = visibleField ? visibleField : field;

// required
if (state.requirements[field.name].required) {
if (field.tagName.toLowerCase() == 'input' && field.value.length == 0) {
errorField.classList.add('inputBorderError');
return false;
} else if (field.value === undefined || field.value === '') {
errorField.classList.add('inputBorderError');
return false;
}
}

// max length
if (state.requirements[field.name].maxlength) {
if (field.value.length > state.requirements[field.name].maxlength) {
errorField.classList.add('inputBorderError');
return false;
}
}

// date
if (state.requirements[field.name].date) {
if (!moment(field.value, ['MM/DD/YYYY', 'YYYY-M-D'], true).isValid()) {
errorField.classList.add('inputBorderError');
return false;
}
}

errorField.classList.remove('inputBorderError');
return true;
}

Let me also provide some simplified HTML so you can see a sample of the function’s usage:

让我还提供一些简化HTML,以便您可以查看该函数用法的示例:

<form id="myForm">
<div>
<input
name="Description"
type="text"
oninput="
if (valid(this)) {
edit(this);
}
"
>
</div>

<div>
<input
name="DueDate"
type="text"
oninput="
if (valid(this, document.getElementById('myForm'))) {
edit(this);
}
"
>

</div>

<button type="submit">Submit</button>
</form>

The function is decently complex, so let’s go over it to make sure we understand what’s happening. We have a valid() function that takes in the parameters field and visibleField. This is used within the context of an HTML form, so the two parameters are HTML elements. We see a variable immediately declared called state. It has a saved property and a requirements property.

该函数非常复杂,因此让我们仔细研究一下以确保我们了解正在发生的事情。 我们有一个valid()函数,它接受参数fieldvisibleField 。 这是在HTML表单的上下文中使用的,因此两个参数是HTML元素。 我们看到一个立即声明为state的变量。 它具有一个已saved属性和一个requirements属性。

// ...
requirements: {
Description: {
required: true,
maxlength: 150
},
// ...
}

One of the immediate issues you may notice is that the saved property in state isn't even used. Instead of confusing you by explaining its original purpose, let's just accept that there was a plan for it upon initial development that was since abandoned, making the saved property an artifact of an old design (it never was cleaned out).

您可能会注意到的直接问题之一是,甚至没有使用处于statesaved属性。 让我们不要接受解释它的原始目的而使您感到困惑的是,我们只接受有一个针对它的计划,该计划在最初的开发中就被放弃了,从而使saved属性成为旧设计的产物(它从未被清除)。

// max length
if (state.requirements[field.name].maxlength) {
if (field.value.length > state.requirements[field.name].maxlength) {
errorField.classList.add('inputBorderError');
return false;
}
}

The keys in the requirements property in the state object are mapped to field names in the form ( Description and DueDate are in our HTML form). The requirements properties' values, which are objects, map to different validations we want to perform on the field. For example, if we have...

state对象中的requirements属性中的键被映射到表单中的字段名称( DescriptionDueDate在我们HTML表单中)。 requirements属性的值(即对象)映射到我们要在该字段上执行的不同验证。 例如,如果我们有...

…our max length if-block catches it and returns false if it fails.

…我们的最大长度if块捕获了它,如果失败,则返回false

We can also see that the function handles displaying the error by adding a class to an element ( errorField.classList.add('inputBorderError')). If a visibleField element is provided, that is what the error is displayed on, otherwise it uses the primary field element.

我们还可以看到该函数通过将类添加到元素( errorField.classList.add('inputBorderError') )处理显示错误。 如果提供了visibleField元素,则显示错误,否则,它将使用主field元素。

If the field passes through all of the validation rules that apply to it without returning false, the function eventually returns true, so the function always returns a boolean.

如果该字段通过了适用于该字段的所有验证规则而没有返回false ,则该函数最终将返回true ,因此该函数将始终返回一个布尔值。

重构 (Refactoring)

function valid(field, visibleField) {
var state = {
// saved: true,
// ...
};
// ...
}

Note: Before we continue, I invite you to make an attempt at improving this function on your own. Feel free to share your solution in the comments along with details of why you did what you did-it might be better than mine!

注意:在继续之前,我邀请您尝试自己改进此功能。 随时在评论中分享您的解决方案以及执行您所做工作的详细信息,这可能比我的要好!

function valid(field) {
var state = {
requirements: {
Description: {
required: true,
maxlength: 150
},
DueDate: {
date: true
},
PriorityID: {},
TypeID: {}
}
};

if (!state.requirements[field.name]) {
return true;
}

// required
if (state.requirements[field.name].required) {
if (field.tagName.toLowerCase() == 'input' && field.value.length == 0) {
return false;
} else if (field.value === undefined || field.value === '') {
return false;
}
}

// max length
if (state.requirements[field.name].maxlength) {
if (field.value.length > state.requirements[field.name].maxlength) {
return false;
}
}

// date
if (state.requirements[field.name].date) {
if (!moment(field.value, ['MM/DD/YYYY', 'YYYY-M-D'], true).isValid()) {
return false;
}
}

return true;
}

First, let’s start with something easy. As I said earlier, the saved property in state is no longer a part of the solution, so let's remove that.

首先,让我们从简单的事情开始。 如前所述, statesaved属性不再是解决方案的一部分,因此让我们将其删除。

Second, I don’t like that this function is handling the displaying of errors when the validation fails. That’s an “invisible” side-effect that makes this function deceptive, and something we should try to avoid as much as possible. Nobody would know that this function does that unless they read the contents of the function, which someone shouldn’t need to do every time they need it. The function is called valid, not validateAndDisplayErrors. It's also an extra responsibility, and we want our functions to be focused. Let's remove the error handling altogether.

其次,我不喜欢该功能在验证失败时处理错误的显示。 这是使该功能具有欺骗性的“隐形”副作用,我们应尽量避免这种情况。 没有人会知道该功能会这样做,除非他们阅读了该功能的内容,否则不必每次有人都需要这样做。 该函数称为valid函数,而不是validateAndDisplayErrors 。 这也是一项额外的责任,我们希望我们的职能得到重点关注。 让我们完全删除错误处理。

function valid(field, validationRules) {

if (validationRules === undefined || validationRules === '')
return true;

// required
if (validationRules.required) {
if (field.tagName.toLowerCase() == 'input' && field.value.length == 0) {
return false;
} else if (field.value === undefined || field.value === '') {
return false;
}
}

// max length
if (validationRules.maxlength) {
if (field.value.length > validationRules.maxlength) {
return false;
}
}

// date
if (validationRules.date) {
if (!moment(field.value, ['MM/DD/YYYY', 'YYYY-M-D'], true).isValid()) {
return false;
}
}

return true;
}

That allowed us to get rid of our second parameter, making our function that much simpler.

这使我们摆脱了第二个参数,使我们的函数变得更加简单。

<input 
name="DueDate"
type="text"
oninput="
if (valid(this, {date:true})) {
edit(this);
}
"
>

Third, while we are removing responsibilities, let’s remove another one. For some reason, this function is hard-coding an object that holds the validation rules for one specific form with our state variable. Let's remove that and make each function call pass the validation rules in for that element. Unfortunately, that means adding a second parameter back in.

第三,当我们删除职责时,让我们删除另一职责。 出于某种原因,此函数对带有state变量的对象进行硬编码,该对象包含一种特定形式的验证规则。 让我们删除它,并使每个函数调用都通过该元素的验证规则。 不幸的是,这意味着要添加第二个参数。

So now our usage looks like this:

所以现在我们的用法如下:

function valid(value, validationRules) {
if (
(typeof validationRules === 'object' && Object.keys(validationRules).length === 0)
|| validationRules === undefined
|| validationRules === ''
) {
return true;
}

// required
if (validationRules.required) {
if (!! value)
return false;
}

// max length
if (validationRules.maxlength) {
if (value.length > validationRules.maxlength)
return false;
}

// date
if (validationRules.date) {
if (!moment(value, ['MM/DD/YYYY', 'YYYY-M-D'], true).isValid())
return false;
}

return true;
}

Fourth, one thing that’s bugging me now is the function being dependent on the HTMLElement interface. That's not good for testing, and it's an unnecessary dependency because the field is no longer being used to handle errors. We are wrestling with different tag types in some instances in order to ultimately get the element's value, so let's just pass the value indirectly and rid ourselves of that cumbersome burden.

第四,现在困扰我的一件事是该函数依赖于HTMLElement接口。 这不利于测试,并且是不必要的依赖项,因为该字段不再用于处理错误。 为了最终获得元素的值,我们在某些情况下会尝试使用不同的标记类型,因此让我们间接地传递值并摆脱麻烦的负担。

This function has improved dramatically from when we started. If you stopped here, you could feel pretty confident in trusting it to accomplish what it needs to. I’m going to take it a little further though.

从开始时起,此功能已得到显着改善。 如果您在这里停下来,您会很有信心去信任它来完成​​所需的工作。 我将更进一步。

Fifth, these if-statement blocks feel primitive. I think we can do better. They lack clarity and readability. Instead what I want to do is break these “validators” out into their own functions so that if we want to edit one or add to them, we only need to modify a small part. This allows us to leave our main function that performs the validation alone.

第五,这些if语句块感觉很原始。 我认为我们可以做得更好。 它们缺乏清晰度和可读性。 相反,我想做的就是将这些“验证器”分解为自己的功能,这样,如果我们要编辑一个功能或将其添加到它们中,我们只需要修改一小部分即可。 这使我们可以保留执行验证的主要功能。

function valid(value, validationRules) {
var validators = {
required: function(value, parameter) {
if (!! value)
return {rule:'required', message:'This field is required.'};

return false;
},

maxlength: function(value, parameter) {
if (value.length > parameter)
return {rule:'maxlength', message:'Maximum length is ' + parameter + ' characters.'};

return false;
},

date: function(value, parameter) {
if (!moment(value, parameter, true).isValid())
return {rule:'date', message:'Not a valid date format, must match ' + parameter + '.'};

return false;
}
};

// ...
}

The thought process I’m describing is derived from the SOLID principles. The O in SOLID is the Open-Closed Principle-open for extension, closed for modification. That means we want to make it easy to extend our validation function by being able to add validators without modifying the existing code. It’s also the S for Single Responsibility Principle because we are breaking our one big function down into smaller immutable methods that have only a single reason to change.

我正在描述的思维过程是从SOLID原理衍生而来的。 SOLID中的O是开闭原理-为扩展打开,为修改而关闭。 这意味着我们希望能够通过添加验证器而不修改现有代码来轻松扩展验证功能。 它也是“单一责任原则”的“ S ”,因为我们将一个大功能分解为较小的不可变方法,这些方法只有一个更改的理由。

I still want to keep the function self-contained; see if you can follow what I’m going to do. I want to keep my validator methods within the valid function. Let’s pull our validators into their own methods in a local object validators.

我仍然想保持功能独立。 看看你能不能遵循我的打算。 我想将我的验证器方法保留在有效函数内。 让我们在本地对象validators中将验证器放入自己的方法中。

function valid(value, validationRules) {
var validators = {
//...
};

// bug fix here
if (validationRules.required === undefined && !value)
return [];

var errors = [];
var result;
for (var rule in validationRules) {
result = validators[rule](value, validationRules[rule]);
if (result) errors.push(result);
}

return errors;
}

We updated the validators to each return an error object with the rule that failed and a default message the user may want to display. Since we aren’t handling the errors in-house anymore, we want to hand back the most information we can that gives the most flexibility to the user. There is a difference between the function doing work that has invisible side-effects and returning data that doesn’t do any work on its own.

我们更新了验证器,以使每个验证器返回一个错误对象,其中包含失败的规则和用户可能希望显示的默认消息。 由于我们不再内部处理错误,因此我们希望提供尽可能多的信息,从而为用户提供最大的灵活性。 在执行具有隐形副作用的函数与返回单独执行任何工作的数据之间存在区别。

Sixth, let’s rework the logic that checks if our value is valid or not based on the validation rules.

第六,让我们根据验证规则重新研究检查我们的值是否有效的逻辑。

Now our valid function returns an array instead of a boolean-it will return an empty array if there are no errors or an array of our error objects that failed validation.

现在,我们的有效函数将返回一个数组,而不是布尔值;如果没有错误或验证失败的错误对象数组,它将返回一个空数组。

While rewriting this part I found a bug-if the validationRules parameter doesn't include a required property, then we shouldn't bother checking the other rules when the value is empty. I labeled the fix above with the "bug fix here" comment.

重写此部分时,我发现了一个错误-如果validationRules参数不包含required属性,那么当该value空时,我们就不应该检查其他规则。 我将上面的修复标记为“此处有bug修复”。

To process our rules, we simply loop through the properties of the validationRules parameter and invoke the corresponding validator. If the result that comes back evaluates to true (because it's an object when validation fails), then we push it into the error’s array.

要处理我们的规则,我们只需遍历validationRules参数的属性并调用相应的验证器。 如果返回的结果评估为true(因为验证失败时它是一个对象),则我们将其推入错误的数组中。

var valid = (function() {
var validators = {
required: function(value, parameter) {
if (!! value)
return {rule:'required', message:'This field is required.'};

return false;
},

maxlength: function(value, parameter) {
if (value.length > parameter)
return {rule:'maxlength', message:'Maximum length is ' + parameter + ' characters.'};

return false;
},

date: function(value, parameter) {
if (!moment(value, parameter, true).isValid())
return {rule:'date', message:'Not a valid date format, must match ' + parameter + '.'};

return false;
}
};

return function(value, validationRules) {
if (validationRules.required === undefined && !value)
return [];

var errors = [];
var result;
for (var rule in validationRules) {
result = validators[rule](value, validationRules[rule]);
if (result) errors.push(result);
}

return errors;
};
})();

Note: I’m aware there are a lack of catches for handling potential issues such as using a non-existent validator in the validationRules, but I want to keep the example straightforward for learning purposes.

注意:我知道没有足够的能力来处理潜在的问题,例如在validationRules使用不存在的验证器,但是出于学习目的,我想保持示例简单明了。

<div id="DescriptionContainer">
<input
name="Description"
value="text"
oninput="
var errors = valid(this.value, {required:true, maxlength:20});

if (errors.length) {
var elErrors = this.nextElementSibling;

var messages = errors.map(error => error.message);
elErrors.innerHTML = errors.join('<br>');
elErrors.classList.remove('hidden');
} else {
elErrors.classList.add('hidden');
elErrors.innerHTML = '';
}
"
>

<div class="errors hidden"></div>
</div>

Seventh, you may be thinking “Hey, every time you call this function you are re-defining every validator method!” Great catch if you did! It’s inefficient to ask the valid() function to define the validators object with all of its methods every time the function is called, so I'm going to turn valid into a variable and assign it to an immediately-invoking, anonymous function that returns a closure. This keeps the validators in the local scope, creates them only one time, and allows me to continue using valid the same way.

第七,您可能在想:“嘿,每次调用此函数时,您都在重新定义每个验证器方法!” 如果您做到了,那就太好了! 每次调用该函数时,都要求valid()函数使用其所有方法来定义validators对象是valid ,所以我将把valid变成一个变量,并将其分配给一个立即调用的匿名函数,该函数返回关闭。 这样会将validators保留在本地范围内,仅创建一次validators ,并允许我以相同的方式继续使用valid validators

That’s going to be our final refactor. Let’s see how the client utilizes our function now.

这将是我们的最终重构。 让我们看看客户端现在如何利用我们的功能。

评论 (Review)

You may be thinking that the way we interact with this function became more complicated since we started, and you’re right. However, our goal here was to fix up a specific function. That involves removing the other responsibilities it had that shouldn’t have been there. Right now that means we moved that responsibility to the client, but that doesn’t mean we can’t write another function that uses our valid function to handle errors for us.

您可能会认为,自从我们开始以来,与此功能交互的方式就变得更加复杂,而您是对的。 但是,我们的目标是修复特定功能。 这涉及到消除原本不应该存在的其他职责。 现在,这意味着我们将责任转移给了客户,但这并不意味着我们无法编写使用valid函数为我们处理错误的另一个函数。

What we can do is use our new valid function as a building block for higher-level functions. If we want to have a function that intentionally has the side-effect of displaying errors, we can utilize our valid function within that. But we keep the validation part decoupled from other responsibilities, such as displaying errors.

我们所能做的就是将我们新的valid函数用作高级函数的构建块。 如果我们想要一个故意显示错误的函数,可以在其中利用我们的valid函数。 但是,我们将验证部分与其他职责(例如显示错误)分离开来。

We also reduced dependencies within the function which greatly expands the usability and flexibility of it. For example, removing our dependency on the HTMLElement interface allows us to use this function for data coming back from an AJAX call before displaying it, which wasn’t possible before.

我们还减少了函数内的依赖关系,从而极大地扩展了其可用性和灵活性。 例如,消除对HTMLElement接口的依赖关系,使我们可以使用此函数处理AJAX调用返回的数据,然后再显示它,而以前是不可能的。

In breaking out the validators and giving each section a single responsibility, we made the function way easier to work with for our future selves and others first getting familiar with it. If we want to add a new validator method, we can see what the input and output of the others are and copy it, or look at how our main processing loop works with them to know how to implement it (In an OO language the validators would likely implement a Validator interface).

在分解验证器并赋予每个部分单一责任时,我们使函数方式更易于与我们将来的用户和其他人一起使用。 如果我们想添加一个新的验证器方法,我们可以看到其他方法的输入和输出并复制它,或者查看我们的主处理循环如何与他们一起工作以了解如何实现它(在OO语言中,验证器可能会实现Validator接口)。

When we build a culture of high coding standards where we can assume a function named valid is only performing validation, we increase trust from the developers working with the code because they don't have to read the contents of every new function they come across to make sure there aren't invisible side-effects or other strange interactions happening. We liberate a significant amount of time and brain-power because of this. The less time spent getting reacquainted with messy, complex functions, the more time spent on better things like new features, learning new skills, and more.

当我们建立高编码标准的文化时,我们可以假设一个名为valid的函数仅执行验证,我们会提高使用该代码的开发人员的信任度,因为他们不必读取遇到的每个新函数的内容确保没有隐形的副作用或其他奇怪的相互作用发生。 因此,我们释放了大量的时间和脑力。 重新了解杂乱而复杂的功能的时间越少,花在更好的功能(例如新功能,学习新技能等)上的时间就越多。

Originally published at https://dev.to on September 11, 2020.

最初于2020年9月11日发布在https://dev.to

翻译自: https://codeburst.io/a-cleaner-code-case-study-9969272182ed

案例研究js图片库代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值