我正在做的一本教程(针对Javascript)建议我们编写如下函数:
function sayHello() {
//Some comments explaining the next line
window.alert("Hello");
}
除了迷惑之外,在现实生活中写这样的东西还有好处吗?如果是这样,有什么好处?
我正在做的一本教程(针对Javascript)建议我们编写如下函数:
function sayHello() {
//Some comments explaining the next line
window.alert("Hello");
}
除了迷惑之外,在现实生活中写这样的东西还有好处吗?如果是这样,有什么好处?
Answers:
如果我有这个错误,请原谅我的记忆... Javascript不是我的首选实现语言。
有几个原因导致人们希望不使用arg函数包装另一个函数调用。尽管可以简单地调用,window.alert("Hello");
而不是直接调用而不是sayHello()
。
但是,如果还有更多呢?您已经打了十几个电话sayHello()
,并写了window.alert("Hello");
。现在,您希望它执行一个window.alert("Hello, it is now " + new Date())
。如果将所有这些呼叫都打包,sayHello()
则将其更改为一个位置。如果没有,请在十几个地方进行更改。这涉及不要重复自己。您之所以这样做,是因为您不想在将来再做十次。
过去,我使用i18n / l10n库工作,该库使用函数对文本进行客户端本地化。考虑sayHello()
功能。hola
当用户本地化为西班牙语时,可以将其打印出来。这可能看起来像:
function sayHello() {
var language = window.navigator.userLanguage || window.navigator.language;
if(language === 'es') { window.alert('Hola'); }
else { window.alert("Hello"); }
}
但是,这不是库的工作方式。相反,它具有一组类似于以下内容的文件:
# English file
greeting = hello
# Spanish file
greeting = hola
然后,库将检测浏览器的语言设置,然后基于适当的本地化文件,以适当的本地化创建动态函数,作为无参函数调用的返回值。
我还没有足够的Javascript编码器来说这是好是坏,只是过去曾经而且可以被视为一种可能的方法。
关键是,将对另一个函数的调用包装在其自身的函数中通常非常有用,并且有助于应用程序的模块化,并且还可能使代码更易于阅读。
撇开所有这些,您正在研究一个教程。有必要在开始时尽可能简单地介绍一些东西。从一开始就引入varargs样式函数调用会导致一些对于通常不熟悉编码的人来说非常混乱的代码。从无参数到参数再到varargs样式要容易得多-每种方法都基于前面的示例和理解。
window.alert
在开发/设计/实现漂亮的模式/弹出窗口之前,它通常也用作占位符,因此,与语言问题一样,可以更轻松地将其切换出来。或者,如果您已经拥有一个,那么几年后的设计更新可能需要进行类似的更改。
除了迷惑之外,在现实生活中写这样的东西还有好处吗?如果是这样,有什么好处?
集中化:尽管实现只有一行,但是如果它经常更改,那么您可能更愿意在单个位置进行更改,而不是在调用sayHello的地方进行更改。
最小化/隐藏依赖项:您的客户端代码不再需要知道有一个窗口对象,甚至可以更改整个实现,而根本不影响客户端代码
合同履行:客户代码可能需要具有sayHello函数的模块。在这种情况下,即使琐碎,功能也必须存在。
抽象级别的一致性:如果客户端代码使用高级操作,则按照“ sayHello,sayBye”和其他一些“ sayXXX”函数(而不是窗口对象)编写客户端代码符合您的利益。实际上,在客户端代码中,您甚至可能不想知道是否存在“窗口”对象之类的东西。
令人惊讶的是没有一个人提到测试。
您选择的特定“包裹”行window.alert('hello')
实际上是一个完美的例子。任何涉及window
对象的测试都非常非常痛苦。在您的应用程序中将其乘以1000倍,我保证开发人员最终将放弃测试。另一方面,sayHello
用间谍破坏函数并测试它是否被调用很容易。
一个更实际的示例-因为实际上,谁真正window.alert(...)
在生产代码中使用?-正在检查系统时钟。因此,例如,它将包装DateTime.Now
在.NET,time(...)
C / C ++或System.currentTimeMillis()
Java中。您真的想将它们包装在可以注入的依赖项中,因为它们不仅(几乎)不可能模拟/伪造,而且是只读且不确定的。任何涉及直接利用系统时钟功能的功能或方法的测试都极有可能遭受间歇性和/或随机性故障的困扰。
实际的包装器是一个1行函数- return DateTime.Now
但这就是您要采取的一个设计欠佳,不可测试的对象,并使之成为干净且可测试的对象。您可以用假时钟代替包装器,然后将其时间设置为所需的时间。问题解决了。
正如Doval所说的,这个例子可能只是试图向您介绍函数。我很一般,但这很有用。特别是通过指定一些而非全部参数,然后传递其他参数,您可以从更通用的函数中创建一个针对特定案例的函数。作为一个比较琐碎的示例,考虑一个排序函数,该函数采用数组进行排序,并使用比较器函数进行排序。通过指定一个按数字进行比较的比较器函数,我可以创建一个sortByNumericalValue函数,并且对该函数的调用更加清晰明了。
您的问题背后的想法似乎是:“为什么不直接写alert("Hello");
?这很简单。”
答案在某种程度上是因为您真的不想打电话alert("Hello")
- 您只想打个招呼。
或者:为什么只拨打电话号码时,为什么要将联系人存储在手机上?因为您不想记住所有这些数字;因为拨打电话既繁琐又容易出错;因为人数可能会改变,但另一端仍然是同一个人。因为您想打电话给别人,而不是电话号码。
这可以通过诸如抽象,间接,“隐藏实现细节”,甚至“表达性代码”之类的术语来理解。
相同的推理适用于使用常量,而不是在各处都写入原始值。您可以在3.141592...
每次需要π时编写,但值得庆幸的是Math.PI
。
我们也可以看看alert()
自己。谁在乎它如何构建和显示该警报对话框?您只想提醒用户。在您编写文字alert("Hello")
和更改屏幕上的像素之间,有很深的代码和硬件堆栈,每一层都告诉下一个所需的内容,下一层负责细节,直到最深的一层将显示内容中的某些内容翻转为止显存。
您真的不想只想打招呼就自己做。
编程-实际上,任何任务都是将复杂的问题分解为可管理的块。解决每个块将为您提供一个构建基块,并且通过足够简单的构建基块,您可以构建大型事物。
它是代数。
最好将值(表达式)的计算与动作(语句)的执行分开。我们希望精确地控制何时何地将采取操作(例如显示消息),但是在计算值时,我们宁愿在更抽象的级别上工作,而不必关心如何计算这些值。
仅使用给定的参数仅计算返回值的函数称为pure。
A“函数”,它执行一个动作实际上是一个程序,它有一个效果。
计算值期间引起的任何影响都称为副作用,最好在可能的情况下避免它们(“我只需要该字符串,我不知道它会锤击数据库!”)。
为了最大程度地减少副作用的发生,我们应该避免向程序发送过多的数据,或者在程序中进行任何计算;如果需要事先执行一些计算,通常最好在纯函数中单独进行计算,然后仅将所需结果传递给过程。这样可以使过程的目的明确,并减少以后在计算中将其重用的可能性(可以重新使用pure函数)。
出于同样的原因,我们应该避免在过程内部处理结果。最好返回我们的操作的结果(如果有),并使用纯函数执行任何后续处理。
如果遵循这些规则,则最终可能会出现类似的过程sayHello
,该过程不需要任何数据,也没有结果。因此,最好的接口是没有参数且不返回值。例如,这比在某些计算过程中调用“ console.log”更好。
为了减少计算过程中对效果的需求,我们可以进行返回过程的计算;例如。如果我们需要决定要采取的行动,我们可以让一个纯函数选择一个过程并返回它,而不是直接执行它。
同样,为了减少过程中的计算需求,我们可以让过程将其他过程作为参数(可能是函数的结果);例如。采取一系列程序并一个接一个地运行。
setFoo
电话,因为这意味着可变的数据。更改变量/属性的内容是一种影响,这会使使用该数据的所有计算变得不纯。我不建议execute
本身调用,但是我会推荐runFoo
程序执行基于他们的论点行动。