当前位置: 首页 > 科技观察

JavaScript代码风格要素

时间:2023-03-12 00:22:32 科技观察

1920年,《英语写作手册:风格的要素(The Elements of Style)》由WilliamStrunkjr编写。发表了。本书列出了7条英语写作指南。一个世纪后,这些指南没有过时。对于工程师,您可以在自己的编码风格中应用类似的建议来指导您的日常编码并提高您的编码技能。重要的是要注意,这些准则并不是硬性规定。如果违反它们使代码更具可读性,那很好,但要小心和反思。这些准则经受住了时间的考验,并且有充分的理由:它们通常是正确的。如果你想违反这些规则,你必须有充分的理由,而不仅仅是一时的兴趣或个人风格偏好。书中写作指导如下:以段落为基本单位:一段文字,一个主题。删除无用的句子。使用主动语态。避免一连串松散的句子。相关内容一起写。使用积极的陈述来陈述。使用不同的结构来解释不同的概念。我们可以将类似的概念应用到代码编写中:一个函数只做一件事,使函数成为代码组合的最小单位。删除不必要的代码。使用主动语态。避免一系列结构松散、难以理解的代码。一起写相关的代码。使用判断真值的方法来写代码。不同的技术方案是通过使用不同的代码组织结构来实现的。1.一个函数只做一件事,使函数成为代码组合的最小单位软件开发的本质是“组合”。我们通过组合模块、功能和数据结构来构建软件。了解如何编写和组合方法是软件开发人员的一项基本技能。模块是一个或多个函数和数据结构的简单集合。我们使用数据结构来表示程序状态。只有在函数执行后,程序状态才会发生一些有趣的变化。在JavaScript中,函数可以分为三种类型:I/O类型函数(CommunicatingFunctions):函数用于执行I/O。程序功能:将指令序列分组。映射函数:给定一些输入,返回相应的输出。有效的应用程序需要I/O,许多程序都遵循特定的程序执行顺序。在这种情况下,程序中的大多数函数将是映射函数:给定一些输入,返回相应的输出。每个函数只做一件事:如果你的函数主要用于I/O,不要在其中混合映射代码,反之亦然。严格按照定义,过程函数违反了这条准则,但它们也违反了另一条准则:避免一串结构松散、难以理解的代码。理想的函数是一个简单的、明确的纯函数:相同的输入,总是返回相同的输出。无副作用。另请参阅“什么是纯函数?”2.删除不必要的代码干净的代码对软件来说至关重要。更多的代码意味着更多的错误隐藏空间。更少的代码=更少的bug隐藏空间=更少的bug干净的代码读起来更清晰,因为它有更高的“信噪比”:阅读代码时更容易筛选出更少的语法噪音真正有意义的部分。可以说更少的代码=更少的语法噪音=更强的代码含义信息传达了从本书借用的一句话《风格的元素》:简洁的代码更健壮。functionsecret(消息){returnfunction(){returnmessage;}};可以简化为:constsecret=msg=>()=>msg;对于熟悉简洁箭头函数编写开发的人来说,可读性更好。它省略了不必要的语法:花括号、函数关键字和返回语句。简化前代码中包含的语法元素在传达代码本身的含义方面并不是很有效。它存在的唯一原因是让不熟悉ES6语法的开发人员更容易理解代码。ES6自2015年以来一直是语言标准,是时候学习它了。删除不必要的代码有时我们会尝试命名不必要的东西。问题是人脑在工作中可用的记忆资源有限,每个名字都必须作为一个单独的变量存储,占用工作记忆中的存储空间。出于这个原因,有经验的开发人员会尽可能删除不必要的变量。例如,在大多数情况下,您应该省略仅用作返回值的变量。您的函数名称应该已经说明了函数的返回值。查看以下内容:constgetFullName=({firstName,lastName})=>{constfullName=firstName+''+lastName;returnfullName;};对比constgetFullName=({firstName,lastName})=>(firstName+''+lastName);开发人员用来减少变量名称的一种常见做法是利用函数组合和无点样式。Point-free-style是一种定义函数的方式,定义为与参数无关的复合操作。实现无点风格的常见方法包括函数柯里化和函数组合。我们来看一个柯里化函数的例子:constadd2=a=>b=>a+b;//现在我们可以定义一个点-freeinc()//thatadds1toanynumber.constinc=add2(1);inc(3);//4取一个查看inc()函数是如何定义的。请注意,它不使用function关键字或=>语句。add2也没有列出参数列表,因为该函数不在内部处理参数列表,而是返回一个知道如何处理参数的新函数。函数组合是将一个函数的输出用作另一个函数的输入的过程。也许你没有意识到,你一直在使用函数组合。链式调用的代码基本都是这种模式,比如数组操作使用.map(),Promise操作使用promise.then()。函数组合在函数式语言中也称为高阶函数,其基本形式为:f(g(x))。组合两个函数时,无需创建变量来保存两个函数运行时的中间值。来看看函数组合是如何减少代码的:constg=n=>n+1;constf=n=>n*2;//需要运算参数并存储中间结果constincThenDoublePoints=n=>{constincremented=g(n);returnf(incremented);};incThenDoublePoints(20);//42//compose2-接受两个函数作为参数,直接返回合成constcompose2=(f,g)=>x=>f(g(x));constincThenDoublePointFree=compose2(f,g);incThenDoublePointFree(20);//42你可以使用仿函数来做同样的事情。将参数封装到仿函数中的可遍历数组中。让我们使用仿函数编写另一个版本的compose2:constcompose2=(f,g)=>x=>[x].map(g).map(f).pop();constincThenDoublePointFree=compose2(f,g);incThenDoublePointFree(20);//42这就是你每次使用承诺链时所做的事情。几乎每个函数式编程类库都提供了至少两种函数组合方法:从右到左运行的compose();从左到右运行的pipe()。Lodash中的compose()和flow()分别对应这两个方法。下面是一个使用pipe的例子:importpipepfrom'lodash/fp/flow';pipe(g,f)(20);//42下面的代码做了同样的事情,但是代码量并没有增加太多:constpipe=(...fns)=>x=>fns.reduce((acc,fn)=>fn(acc),x);pipe(g,f)(20);//42如果函数组合听起来是奇怪,你不知道如何使用它,想一想:软件开发的本质是组合,我们通过组合更小的模块、方法和数据结构来构建应用程序。不难看出,工程师理解功能和对象组成的编程技能与理解钻孔机和装饰用气枪一样重要。当你使用“命令式”代码将函数和中间变量拼凑在一起时,就像疯狂地使用胶带和胶水粘贴这些部分,函数组合看起来更加流畅。记住:使用更少的代码。使用更少的变量。3.使用主动语态主动语态比被动语态更直接、更有力。尽量直接命名:myFunction.wasCalled()优于myFunction.hasBeenCalled()createUser优于User.create()notify()优于Notifier的布尔返回值命名时直接反映输出类型.doNotification():isActive(user)优于getActiveStatus(user)isFirstRun=false;优于firstRun=false;函数名称使用动词形式:increment()更好inplusOne()unzip()优于filesFromZip()filter(fn,array)优于matchingItemsFromArray(fn,array)事件处理事件处理和生命周期函数是限定符,是特殊的,所以动词形式规则不适用;与“whattodo”相比,它们主要用来表达“whentodo”。对于它们,你可以像“,”这样的命名,这样比较朗朗上口。element.onClick(handleClick)优于element.click(handleClick)element.onDragStart(handleDragStart)优于component.startDrag(handleDragStart)上面两个例子的后半部分读起来更像是在尝试触发一个事件,而没有回应。生命周期函数对于组件生命周期函数(在更新组件之前调用的方法),请考虑以下命名:更新)。这种方式很口语化,但意思并不比其他两种方式更明确。第二个好很多,但是生命周期函数的重点是触发处理事件。componentWillUpdate(handler)读起来好像它会立即触发一个handle事件,但这不是我们想要传达的。我们想说,“在更新组件之前,触发事件”。beforeComponentUpdate()可以更清楚地表达这个想法。进一步简化,因为这些方法内置于组件中。将组件添加到方法名称是多余的。想想如果你直接调用这些方法:component.componentWillUpdate()。这就像在说,“吉米吉米晚餐吃了牛排。”您不必两次听到同一个主题的名字。很明显,component.beforeUpdate(doSomething)优于component.beforeComponentUpdate(doSomething)功能混合是指将方法作为属性添加到对象中,它们就像流水线一样,将某些方法或属性添加到传入的对象中。我喜欢用形容词来命名函数mixins。您还可以经常使用“ing”或“able”后缀来查找有意义的形容词。例如:constduck=composeMixins(flying,quacking);constbox=composeMixins(iterable,mappable)4.避免一系列结构松散、难以理解的代码开发人员经常将一系列事件串联在一个进程中:一组松散的、独立的、不相关的代码旨在顺序运行。这使得形成“意大利面条”代码变得容易。这种写法常被反复调用,即使不严格重复,也只有细微的差别。例如,界面的不同组件共享几乎相同的核心要求。它的关注点可以分解为不同的生命周期阶段,并通过单独的功能方法进行管理。考虑以下代码:constdrawUserProfile=({userId})=>{constuserData=loadUserData(userId);constdataToDisplay=calculateDisplayData(userData);renderProfileData(dataToDisplay);};这个方法做了三件事:获取数据,根据获取到的数据计算视图的状态并渲染。在大多数现代前端应用程序中,这些问题中的每一个都应该被单独考虑。通过拆分这些关注点,我们可以轻松地为每个问题提供不同的功能。例如,我们可以完全替换渲染器而不影响程序的其他部分。例如,React丰富的自定义渲染器:用于原生iOS和Android应用程序的ReactNative、用于WebVR的AFrame、用于服务器端渲染的ReactDOM/Server等等……drawUserProfile的另一个问题是您不能在使用数据的情况下,只需计算要显示的数据并生成标签。如果数据已经加载到别处怎么办,就会做很多重复浪费的事情。拆分关注点也使它们更容易测试。我喜欢对我的应用程序进行单元测试,并在每次修改代码时查看测试结果。但是,如果我们把渲染代码和数据加载代码写在一起,我就不能简单地把一些假数据传递给渲染代码进行测试。我必须从头到尾测试整个组件。在这个过程中,由于浏览器加载,异步I/O请求等都会耗费时间。上面的drawUserProfile代码没有从单元测试中得到即时反馈。拆分功能点允许您进行单独的单元测试并获得测试结果。上面已经分析了各个功能点,我们可以提供不同的生命周期钩子在应用中调用。当应用程序开始加载组件时,可以触发数据加载。可以基于响应式视图状态更新来触发计算和渲染。这样做的结果是进一步明确了软件的职责:每个组件都可以重用相同的结构和生命周期钩子,软件性能更好。在后续的开发中,我们不需要重复同样的事情。5.功能相关的代码写在一起许多框架和样板指定了程序文件组织的方法,文件根据代码类别进行分组。如果您要构建一个小型计算器,获取一个待办事项应用程序,这很好。但对于较大的项目,按业务功能属性将文件分组在一起是更好的方法。按代码类别分组:.├──components│├──todos│└──user├──reducers│├──todos│└──user└──tests├──todos└──userbybusinessfunctionfeatures分组:.├──todos│├──component│├──reducer│└──test└──user├──component├──reducer└──test当你按功能分组文件时,你可以避免向上滚动并在文件列表中找到您需要编辑的文件。6、用判断真值的方法写代码,做出肯定的断言,避免使用温顺、没有色彩、犹豫不决的语句,必要时使用不否定或拒绝。典型的isFlying优于isNotFlyinglate优于notOneTimeif语句if(err)returnreject(err);//dosomething优于if(!err){//...dosomething}else{returnreject(err);}ternary判断语句{[Symbol.iterator]:iterator?iterator:defaultIterator}优于{[Symbol.iterator]:(!iterator)?defaultIterator:iterator}适当使用否定有时候我们只关心一个变量是否缺失,如果真值判断同理命名,我们要用!运营商否定它。在这种情况下使用“not”前缀和否定运算符不如使用否定语句直接。if(missingValue)优于if(!hasValue)if(anonymous)优于if(!user)if(!isEmpty(thing))优于if(notDefined(thing))调用函数时,避免使用null和undefined而不是某物调用函数时,不应将undefined或null作为参数值传递给参数。如果可以少一些参数,建议传入一个对象:constcreateEvent=({title='Untitled',timeStamp=Date.now(),description=''})=>({title,description,timeStamp});constbirthdayParty=createEvent({title:'BirthdayParty',description:'Bestpartyever!'});比constcreateEvent=(title='Untitled',timeStamp=Date.now(),description='')=>({title,description,timeStamp});constbirthdayParty=createEvent('BirthdayParty',undefined,//这是可以避免的'Bestpartyever!');不同的技术方案采用不同的代码组织结构来实现,到目前为止,应用中未解决的问题很少。最终,我们都在一遍又一遍地做同样的事情。当出现这样的场景时,就意味着代码重构的机会来了。识别相似的部分,然后提取支持每个不同部分的通用方法。这正是库和框架为我们所做的。UI组件就是一个很好的例子。10年前,使用jQuery编写混合界面更新、应用程序逻辑和数据加载的代码非常普遍。渐渐地,人们开始意识到我们可以将MVC应用于客户端网页,然后人们开始将模型和UI更新逻辑分离。最后,组件化在Web应用程序中的广泛采用使我们能够使用JSX或HTML模板以声明方式对组件进行建模。最终,我们可以用完全相同的方式表达所有组件的更新逻辑和生命周期,而不必写一堆命令式代码。对于熟悉组件的人来说,很容易理解每??个组件的原理:使用标签来表示UI元素,事件处理程序用于触发动作,生命周期钩子用于添加必要时将运行的回调。当我们使用相似的模式来解决相似的问题时,熟悉这种解决模式的人可以很快理解这段代码的用途。结语:代码要简单,不能过于简单虽然在2015年,ES6已经标准化,但是在2017年,很多开发者仍然拒绝使用ES6的特性,比如箭头函数、隐式返回、rest和spread运算符等,这是错误的说用你熟悉的方式写代码其实是假的。只有不断尝试,才能逐渐熟悉。熟悉之后,你会发现简洁的ES6特性明显优于ES5:与强调语法结构的ES5相比,简洁的ES6代码非常简单。代码应该简单,而不是过于简单。简洁的代码有以下优点:bug少更容易调试,但也有以下缺点:修复bug的成本较高,可能引用的bug较多,打断了正常的开发过程。简洁的代码也是:更容易写,更容易阅读保持一个明确的自己的目标,不要毫无头绪。毫无头绪是浪费时间和精力。投资于培训,熟悉自己,学习更好的编程方式,以及更动态的编码风格。代码应该简单,而不是简单化。