function numberPlusOne(val){
if(typeof val === 'string') {
val = parseFloat(val)
}
if(typeof val === 'number'){
if(!isNaN(val)) return val + 1
}
return NaN
}
function oneDayOfWorker(){
init() //非常想吐槽的函数名init
}
function init(){
leaveHome()
}
//假设以下行为均是异步的
function leaveHome(){
doSomeThing(work)
}
function work(){
doSomeThing(goHome)
}
function goHome(){
doSomeThing(sleep)
}
好的例子
function oneDayOfProgramer(){
leaveHome(()=>{
work(()=>{
goHome(sleep)
})
})
}
function leaveHome(callback){
doSomeThing(callback)
}
function work(callback){
doSomeThing(callback)
}
function goHome(callback){
doSomeThing(callback)
}
更好的例子
async function oneDayOfProgramer(){
await leaveHome()
await work()
await goHome()
sleep()
}
function transformPromise(fn){
return new Promise(resolve=>{
fn(resolve)
})
}
function leaveHome(){
return transformPromise(doSomeThing)
}
function work(){
return transformPromise(doSomeThing)
}
function goHome(){
return transformPromise(doSomeThing)
}
什么是好的函数?
这要从结果上来评价一个函数的好坏。先考虑写完一个函数,它有哪些结果?
可执行
这是最基本的,函数不能运行那就没有意义。
保障函数可执行,要从两个方面考虑:函数本身逻辑、函数执行环境。函数本身逻辑可执行不用多说,函数执行环境是容易遗漏并出错的:函数如果接收参数,那么就要考虑参数的数据类型是否符合运行要求;函数如果调用外部变量、函数,就要考虑外部变量是否存在且符合要求,外部函数是否能正常工作。对这些情况的处理能力称为健壮性。
换个角度考虑,如果写的这个函数在程序中没有被调用过,那它就是应当删除的冗余代码,应当减少。如果这个函数被调用一次以上,它就是有价值的代码。如果被多次调用,那它就具备复用性,价值进一步提升。
完成功能
这是第二个基本,函数没有完成它该有的功能,那它的意义也是值得怀疑的。
进一步考虑,如果函数没有完成被期望的功能,却干了别的出人意料的事,那它简直是老鼠屎,扰乱了程序的执行逻辑。
提炼一下:“被期望的功能”意味着函数是有姓名的,在函数名中应当体现出来,这就是语义。函数不应当做出“别的出人意料的事”,这就是副作用,应当避免。
可阅读
衡量可阅读程度的名词,一般称为可读性。可读性是现代程序语言发展的根本,从二进制,到汇编等低级语言,到今天百家争鸣的高级语言,可读性一路攀升。按理说,高级语言的可读性已经远高于低级语言了,为什么编程时还要注意可读性?
试想一下反面例子:Web前端如何保护代码资产?
就目前客户端浏览器“三大件”HTML、CSS、JavaScript而言,保护代码资产是不可能实现的。所有的解决方案归纳为“降低可读性”,让人难以阅读,就一定程度上做到了保护代码资产,不让人理解进而进行修改和维护。
相反地,提高可读性,就是为了方便自己或他人理解以及进行修改和维护。
由此,一个好的函数,它应当是
怎样写好函数
本文以JavaScript为例,从健壮性、复用性、语义、副作用、可读性五个方面举例说明。
健壮性
坏的例子
期望是对输入数字,返回数字加1后的结果。但如果输入的不是数字,而是数字字符串,或者是非数字的其他内容呢?
好的例子
如果有大数相加需要,还得进一步考虑JavaScript计算精度问题。
复用性
坏的例子
期望是格式化产品的两个价格字段price、originalPrice,两个字段处理方式一致。
好的例子
复用性的基本内容就是避免重复代码。但在编程过程中,它应当是值得考虑的优化方案,而不是奉为圭臬的必须方案。提前考虑复用,结果由于各种原因没有被复用到,实际是没有提高复用性,反而可能降低开发效率。
语义
坏的例子
期望是计算两数相加(add)的结果,即求和(sum)。
好的例子
那么
add
应当如何满足其语义呢?add语义是“增加”,sum语义是“合计”,意义是不同的。编程所需的语义,是建立在能够正确理解语言意义基础上的。所以说,程序员是需要学好英语的。 上例说明的是函数名的语义不恰当问题,编程中常见的问题是给常量、变量、字段命名,有时候还会纠结多个相似的值,如何区分命名。
副作用
期望是“对象合并”,两个函数都实现了对象合并,并返回合并后的对象。
extendWithSideEffect
的副作用是会改变输入参数obj1
对象内容,在当前期望中是副作用,应当避免。可读性
坏的例子
好的例子
更好的例子
这个例子主要说明的可读性问题是,避免“链式”编写函数,而应当以“总-分”的结构去组织函数。
“链式”编写函数:
描述为主函数中只调用开始的子函数,在子函数定义中去调用其他子函数,形成“链表”结构。代码读者需要逐个子函数地查看以理解主函数main的功能逻辑。
“总-分”结构组织的函数:
描述为主函数中描述了子函数调用顺序,子函数定义各自实现功能。代码读者可以根据主函数main,结合子函数名的语义理解功能逻辑。
上面的问题是一种影响可读性的典型问题。可读性需要注意的问题不止一种,还有些问题可能存在争议需要统一意见,因此有着“代码风格”之说,不同风格有差异也有共同之处,多做了解和比较,整理出自己心目中的最佳实践吧!
结束语
“如何写好函数”是一个偏主观的话题,在编程实践中程序员们积累了大量客观的评价指标,其中有些指标可能是相互制约的,例如复用性、可扩展性、可读性,三者就不容易共同提高。所以这类问题鲜少有“最佳实践”的讨论。
但是,写好函数的重要性是不言而喻的。“编程一时爽,重构火葬场”,坏的函数要么影响程序员上班的心情,要么提前下次重构的计划到来,两者都不是什么好事。何以解忧?唯有换行。嗯,换行是有条提升可读性的代码风格规范。
反观自身,如何评价自己的代码好不好?笔者的建议是,阅读当前编程语言最流行的一些框架、库的源码,阅读过程中去思考如果自己来写,能不能写得更好。本文正是读源码过程中有感而发。