简单是否会始终提高可读性?


32

最近,我正在为我们的公司开发一套编码标准。(我们是一个新团队,正在为公司扩展一种新语言。)

在我的初稿中,我设定了编码标准的目的是提高可读性,可维护性,可靠性和性能。(我忽略了可写性,可移植性,成本,与以前的标准的兼容性等)

编写本文档时,我的目标之一是推动代码简化的想法。想法是每行只能有一个函数调用或操作。我希望这将提高可读性。我从以前的语言中继承了这个想法。

但是,我对这种推动背后的假设提出了质疑:

简单是否会始终提高可读性?

是否存在编写更简单的代码会降低可读性的情况?

这很明显,但是“简单”并不是说“易于编写”,而是每行更少的内容。


16
如果替代方法是“聪明”代码,则可以...
Oded

2
是的-每奥卡姆剃刀- en.wikipedia.org/wiki/Occam%27s_razor
aggietech

17
尽量避免总是和永远不要使用这样的僵化术语。这是为了避免侧重于极端情况,而侧重于我们面临的最常见问题。这就是最佳实践的全部意义。
P.Brian.Mackey 2011年

实际上,您需要每行2个功能/操作。a = b是一个操作,b + c是第二个操作,这意味着a = b + c是2个操作。链接2个功能/运算符仍然可读:foo(bar())a = foo()
zzzzBov 2011年

2
另外,不要太担心。如果您试图从解释中消除主观性,就像您尝试以一百万或更多规则指定编码风格的每个可能细节一样,那么您的标准将过于复杂,难以理解,被忽略,因此毫无意义。
2011年

Answers:


47

“简单”是一个过度使用的词。可以将“可读”定义为“易于理解”,在这种情况下,通过定义来提高(这种度量)简单性可以提高可读性,但是我认为这不是您的意思。我写的 关于这个其他地方,但一般的东西可以被称为“简单”无论是通过更抽象的(在这种情况下,较少的概念可以表达更多的现象),或者通过更具体的(在这种情况下,概念并不需要太多的背景首先要了解的知识)。我认为,从角度来看,可以将更抽象的概念称为比更具体的概念更简单的概念,反之亦然。即使“抽象”和“

我以一些我之前编写的Haskell代码为例。我问了一个关于stackoverflow的问题,该问题涉及使用List monad计算一个计数器,其中每个数字可以具有不同的底数。我最终的解决方案(不太了解Haskell)如下所示:

count :: [Integer] -> [[Integer]]
count [] = [[]]
count (x:xs) =
  -- get all possible sequences for the remaining digits
  let
    remDigits :: [[Integer]]
    remDigits = count xs
  in
  -- pull out a possible sequence for the remaining digits
  do nextDigits <- remDigits
     -- pull out all possible values for the current digit
     y <- [0..x]
     -- record that "current digit" : "remaining digits" is
     -- a valid output.
     return (y:nextDigits)

答案之一将其减少为:

count = mapM (enumFromTo 0)

其中哪一个更“容易理解”(即更具可读性)完全取决于读者对(抽象的)单子操作(以及就此而言,无点代码)的适应程度。熟悉这些抽象概念的读者可能会喜欢阅读(简短的)更抽象的版本,而那些不熟悉这些操作的读者会喜欢阅读(更长的)更具体的版本。关于哪个版本更适合所有人阅读,没有一个答案。


11
一些开发人员会不断进步,直到他们理解并喜欢单线。很难想象有一个开发人员了解单行开始喜欢长版本。因此,IMO单线显然更好。
凯文·克莱恩

7
@kevincline-假设开发人员正在孤立地工作,我同意,较短的版本(可能)更好。如果她是团队的一员,并且团队不在他们理解和偏爱单一代码的级别,并且他们都必须能够维护代码,那么在这种情况下,长版本会更好(可能) 。
艾丹·库利(Aidan Cully)

6
相对而言:鉴于您是一位经验丰富的Haskeller,可以一眼阅读第二版。另一方面,第一个版本需要阅读和理解更多的代码。您无法一目了然。我认为这使第二个版本更简单。
蒂洪·耶维斯

6
@ Steve314:mapM很惯用,并且enumFromTo确实按照罐头上所说的去做。总的来说,我发现扩展代码实际上更容易出错,一个错误就多了-只是有更多的代码会出错。这在诸如for循环与其他语言的高阶过程之类的事情中尤其明显,但我发现这是一个普遍原则。
迪洪·杰维斯

1
@Tikhon-但这并不一定意味着要阅读那么多代码,而这些代码就在那儿。意味着可能需要权衡。通常,一方面倾向于使用现有功能,而不是重新发明轮子,但也有例外。在C ++中,使用标准库中一些更简单的“算法”,这一点变得非常明显-使用它们有时可能比直接编写代码更冗长而不清楚。
Steve314 2011年

13

如果您的编码标准是关于“可读性,可维护性,可靠性和性能”的 则只需说明一下

不要尝试规定如何实现这些目标,因为在某些情况下总会有反例。

您需要规定的是如果不遵守也将导致代码中断的事情。风格特征不会破坏代码,应该作为建议(只要大多数团队同意它是可读性,其余应该是开发人员的偏爱(通过代码审查,以便同仁压力提醒人们其他人也需要阅读代码)) 。


那是我的目标,这是既定目标。该目标自然而然地遵循了简单性(或者说,每行一个功能/操作)。我正在尝试确定我的理解是否无效。在建立编码标准时,建立一套规则和准则是整个练习的重点。设置过于模糊的规则和准则是没有用的。因此,这个答案确实没有帮助。
理查德

5
我的观点是,制定过于严格的规则要比没用的糟糕,实际上是有害的。设置规则(如每行一条语句)是风格上的。这绝对不是代码准则中应包含的内容。它没有提供实际的好处,如果不加考虑就对可读性和可维护性有害。
马丁·约克

3
+1(因为我不能+10)我在新的编程经理那里看到的一个常见错误是,他们试图将每个细节都编成代码。最好的编码标准更像是幸运饼干,而不是食谱。
JohnFx 2011年

该文件的名称为“编码样式和标准”。显然,这不是一种标准(例如“从不使用GoTo”或“从不使用短整数”),而是一种样式。统一样式对于可读性和可维护性很重要。
理查德

1
样式指南:“此项目使用制表符/空格(选择一项)。此项目使用大括号样式K&R / Allman / BSD / GNU(选择一项)。请不要在行尾添加空格。请保持代码清晰易读。所有内容都将由两名团队成员和您自己进行代码审查:为了提高可读性/可维护性,您需要占2/3的多数才能检入代码;对于可靠性和性能,则需要3/3的代码(请提供适当的测试以证明)。如果滥用这些规则,则会添加更多规则:-)“完成。
马丁·约克

7

更少的“每行填充”,简单性和可读性不是一回事。可以采用一种极其复杂的,晦涩的,未记录的算法,并用每行1条语句(而不是2条)对它进行编码,并且它的可读性不会那么高。

更少的“每行填充”还可能需要为开发人员提供高大的监视器,以查看现在更垂直地分布的代码块。或因阅读字体较小而引起眼睛疲劳。

可读性是它自己的指标,通常需要在其他几个更容易测量的指标之间进行折衷。预约束所有其他度量,并且不再可能做出折衷,从而导致代码可读性降低。


5

总是?-没有

具有讽刺意味的是,达到适当的简单度可能是一项复杂的工作。我认为关键是要适度。旁观者也可以看到简单性,因此,如果您发现自己的想法过多,则可以不理会它,或者稍后再回来。

就个人而言,当我尝试回过头来简化我写的东西时,我专注于改变主意或尝试了几件事来获得自己想要的东西的领域。这些区域通常可以被平滑。然后,只需对代码进行几次传递,以使其更具可读性,而又不会分散太多内容,以至于您无处不在以找出调试中发生的事情。


5

如果我为简单起见,可以编写如下代码:

10000000000000000000000000000000000000000000

但是,如果我追求可读性,我会更喜欢:

1e43

另一方面,除非您一直使用科学计数法来处理数字,否则1000它更具可读性和简单性1e3

这是一个几乎可以在任何地方找到的更通用模式的简并的示例-从非常简单的块中构建内容可能会以许多不同的方式迅速变得难以理解/效率低下/不良。另一方面,泛化和重用一开始比较困难(有人可能会说“ wtf是e?!是要写1343吗?”),但从长远来看会有所帮助。


关于“ 1e3”的可读性不如“ 100”的观点,您的观点很明确。实际上,这是认知负荷如何影响可读性的一个很好的例子。“ 1e3”要求读取3个字符,并转换幂。读取“ 100”需要读取3个字符,并且无需进一步评估。
斯蒂芬·格罗斯

斯蒂芬,实际上,当100您像读取三位数字时,必须执行两次乘法和两次加法(0+10*(0+10*1))。但是,习惯了这种表示法后,您甚至都没有注意到。这再次表明了简单性概念的主观性。
Rotsor 2011年

有趣。因此,严格来说,“ 100”需要2个乘法(1 * 100、0 * 10)和2个加法运算。“ 1e3”需要一个乘法运算。因此,在没有任何认知上下文的情况下,“ 100”比“ 1e3”更难读。但!如果包括上下文切换认知负荷,则计算将有所不同。由于我们通常以非科学形式读取整数,因此“ 100”会更容易。但是,如果我们正在编写一个工程应用程序,其中所有数字均以科学计数法表示,则“ 1e3”形式会更容易!
斯蒂芬·格罗斯

2

不必要。如果您有一个复杂的操作,那么该复杂性就必须解决。减少一行中“填充”的数量仅意味着它将占用更多行,如果这会使您的例程太“高”而无法容纳在一个屏幕上,那么实际上这将有害于代码的可读性。


复杂性不是不可减少的。我们有“抽象”,“组块”和“组织”来尝试管理复杂性。我认为可以用许多简单的步骤来描述一个复杂的操作。它适用于许多现实世界的物理过程:摘要,概述等
。– S.Lott

1
@ S.Lott:是的,但是足够的滚动和Ctrl单击可能会使“简化”的过程更加难以遵循。我已经看到它发生过一次或两次(虽然不常见,但是当它走得太远时,使用它会非常令人沮丧)。
FrustratedWithFormsDesigner

3
@ S.Lott-复杂程度可以降低多少。您可以消除不必要的复杂性,但只能管理(而不是消除)必要的复杂性-需求固有的复杂性。可以说,管理复杂性的机制也增加了复杂性-将不相关的复杂性移开以更好地揭示特定方面/问题/构造的相关细节会涉及一些增加的复杂性。
Steve314 2011年

1
@ S.Lott-的确,您可以使用零复杂度(完全为空)的源文件表示您想要的任何需求。但是,由于您需要一种非常特定的语言来满足您的要求,因此您要做的就是将需求移至语言规范中。
Steve314 2011年

1
@ S.Lott-如果您声称可以在圣诞节那天预测木星的位置,则仅此而已G=0,我认为您很疯狂。如果不是的话,您就错过了我的意思。当然,您可以抽象出不相关的细节,但是如果您的要求说是相关的,则不是无关紧要的。如果您读过一遍,我从未宣称所有复杂性都是必要的-只是某些复杂性是需求中固有的,无法消除。
Steve314 2011年

2

清晰度+标准+代码重用+好的注释+好的设计 可以提高可读性

简单性并不总是由开发人员掌握的,因为这些天算法的性质和应用程序结构的复杂性。

以执行简单任务的简单网页为例。在给定排序例程的情况下,不可能简化逻辑,但是可以通过注释,使用有意义的变量名,以结构化的方式编写等来使其更清楚。


2

简单是否会始终提高可读性?

否。我已经看到很多情况,在同一条线上完成多项简单的工作比在多行中进行复杂。

在更少的代码和更简单的代码之间进行权衡。

通常,除非您确定用更少的行来做比较好,否则我建议您使用更简单的代码。我宁愿使用“过于冗长”的代码而不是“过于复杂”的代码。


1

“使事情尽可能简单,但不要简单” -阿尔伯特·爱因斯坦的常译为

简单可以改善所有事情。当然,对于简单性的不同价值。更少的代码行吗?也许。它是较小的可执行文件吗?可能吧 您的团队需要达成共识吗?绝对可以


+1为引号,但是简单性会提高可读性吗?
理查德

“使事情尽可能简单然后简化”是一个更好的表述,因为软件工程师倾向于过度设计。
mattnz 2011年

1
我希望人们不要再说“让事情尽可能简单,但不要简单”。在我们进行无用的通用工程技巧时,我们是否至少不能推广到MakeAsGoodAsPossibleButNoMoreSo <Thing,Attribute>?(对不起,@ Jesse C. Slicer,您不是唯一引用此内容的人,因此,您真的不应该比其他任何人都更应该得到迷你蚁)。
psr

1

简单是否会始终提高可读性?是。每行一个语句总是更简单吗?不会。相当多的语言都具有三元运算符,一旦掌握了三元运算符,就比等效的if / else分配更简单易懂。

在允许在一行上设置多个变量的语言中,这样做比同等的方法通常更容易理解。

又如:正则表达式做了很多,通常只有一条线路,没有一个正则表达式相当于经常难读。/ \ d {3} [-] \ d {3}-\ d {4} /至少等效于带有多个注释的函数调用,并且比相应的函数主体更易于理解。


1

可读性和简单性是主观术语,取决于个人和上下文,通常但并非总是同时使用。

更为客观的术语是简洁-从原理上讲,您可以通过计算字符数来进行计数,尽管这样做存在一些缺陷。简洁似乎意味着简单性和可读性,但是(至少在我看来)有例外。

如果更长的代码段(可以说更复杂)可以更好地表达其意图,则可以使其更具可读性。您对简单性的定义是否关心意图是另一种主观的事情-例如,您可以根据语法结构和信息理论的熵来定义复杂性,而根本不涉及意图。

因此,可以选择一个较长的变量名...

  • 通过更好地表达意图来提高可读性
  • 降低简洁性-毕竟更长
  • 完全不影响语法简单性-代码的语法结构不变

同样,我可能会写if (some_boolean == true)。与同等替代方案相比if (some_boolean),此...

  • 减少简洁
  • 降低句法简单性,但是
  • 可以通过更好地表达意图来提高可读性。

当然,这将引发大规模抗议活动-对很多人来说,这也总是损害可读性。对我来说,这在很大程度上取决于布尔值的来源-例如,变量名(或方法名或其他名称)可能无法清楚地表示该值是“真值”。当然,if告诉您的东西,但仍然闻起来。但是很多人会因为这个而把我称为白痴。

当然,这进一步证明了整体主观性。


1

你们都缺少一些基本定义。从sim-plex的根部看,简单意味着1折。简单意味着要做一件事。很简单,从根本上缓解,手段附近说谎。容易意味着它就在眼前。其他答案中给出的简单代码示例与所显示的不完全相同。

以非常大的值显示旋转显示。他说这很简单。据我估计,这并不简单。这简单。现在可以键入所需的0数。

10000000000000000000000000000000000000000000

可读版本很简单。它做一件事。它通过为此功能构建的标记目的描述其大小来表示数字。

1e43

您能描述Aidan的 “简单”代码段是做什么的吗?它包含10行代码(不计算注释)和至少7个块(按我的计算)。如果您遵循注释,您会发现它至少可以完成4件事!

count :: [Integer] -> [[Integer]]
count [] = [[]]
count (x:xs) =
  -- get all possible sequences for the remaining digits
  let
    remDigits :: [[Integer]]
    remDigits = count xs
  in
  -- pull out a possible sequence for the remaining digits
  do nextDigits <- remDigits
     -- pull out all possible values for the current digit
     y <- [0..x]
     -- record that "current digit" : "remaining digits" is
     -- a valid output.
     return (y:nextDigits)

但是,重写此代码的建议之一是声明。Aidan确实声明读者必须或必须熟悉Monadic语句,无指针代码等。这很好。这些概念是单一的且独立学习。

count = mapM (enumFromTo 0)

你会发现,真正的简单代码比更易读代码,因为它只做一件事。您可能需要开始学习更多的“一件事”才能理解简单的代码。但是它应该总是更具可读性。


1

简单是否会始终提高可读性?

我会说,也许有一点争议,但绝对不会

您可以在公共界面上给我一个具有200个成员函数的类,它可能是那里最容易理解的公共界面。随便阅读此类代码及其文档可能会很高兴。但是,我不会将其称为“简单”的,因为尽管可读性强,但我仍必须关注所有这些功能如何相互交互,并可能提防因滥用而导致的棘手情况。

我更喜欢一个类,它具有20个成员函数,而这些成员函数不那么容易阅读到200个,因为在防止人为错误和提高代码的可维护性方面,“可读性”并不是我的头等大事。我们可以更改它,即)。

但是,所有这些都取决于我们对“简单性”的个人定义。“可读性”通常不改变似地在我们中间,除非有人已经获得了这么多的专业知识和流畅性,他们认为正则表达式是非常“可读性”,例如,忘记我们凡人的其余部分。

简单

很久以前,有一次我认为“简单”的意思是“尽可能容易阅读”。因此,我将编写具有许多便利功能的C代码,以尝试改善语法并使其更易于读写。

结果,我设计了非常庞大,丰富的高级库,试图为每一种自然人的思想建模一个函数:一个又一个又一个的助手,所有这些都使客户代码具有更易读的语法。那时我写的代码可能是最“易读的”,但也是最“不可维护的”和“复杂的”。

Lisp

然而,在90年代中期(后期),我对LISP产生了短暂的热情。它改变了我对“简单性”的整个想法。

LISP 不是最易读的语言。希望没有人认为在调用带有嵌套括号的递归函数时提取CDR和CAR是非常“可读的”。

但是,在努力使我的语言陷入奇怪的语法和完全递归的处理方式之后,它永久地改变了我的简单性观念。

我在LISP中编写的代码发现,我不再犯任何细微的错误,尽管以这种方式的棘手使我犯了更多公然的错误(但这些错误很容易发现和纠正)。我并没有误解函数的功能,而忽略了一个微妙的,无法预料的副作用。在进行更改和编写正确的程序方面,我只是度过了轻松的时间。

在LISP之后,对我来说,简单性变成了极简主义,对称性,灵活性,更少的副作用,更少但更灵活的功能,这些功能以无限多种方式结合在一起。

我开始欣赏这种心态,即最可靠的代码是不存在的代码。尽管这只是一个粗略的度量标准,但我倾向于根据其数量来查看代码不可靠的可能性。寻求最大的句法便利性和可读性往往会大大增加该数量。

极简主义

有了LISP的思维定式,我开始偏爱极简API。我更喜欢一个库,它比提供大量“便捷”助手的库更少,但更可靠,更灵活,功能更不方便且更难阅读,而这可能会使代码易于“阅读”,但有可能绊倒对这数千种功能之一的误解会导致更多的不可靠性和意外问题。

安全

关于LISP的另一件事是安全性。它促进了最小的副作用和纯净的功能,即使在10秒钟后我仍能发现更明显的错误,但我再也看不到自己犯了细微的错误。

只要我能负担得起,纯函数和不可变状态就对我来说更可取,即使以下语法:

sword = sharpen(sword)

...比以下方式更简单,与人类思维分离:

sharpen(sword)

可读性VS。简单

再一次,LISP不是最“易读”的语言。它可以将很多逻辑打包到一小段代码中(每行可能不止一个人的想法)。理想情况下,我倾向于为“可读性”选择每行一个人的思想,但不一定出于“简单性”。

使用“简单”的这种定义,有时“简单”实际上可能在某种程度上与“可读”竞争。这是从界面设计的角度考虑的更多方面。

一个简单的界面意味着您需要学习更少的东西来使用它,并且由于其极简主义而可能具有更高的可靠性和更少的陷阱。关于该主题的全面文档可能适合一本小册子,而不是大量的书籍。但是,这可能需要做更多的工作,并产生可读性较低的代码。

对我来说,“简单”可以提高我们广泛理解系统功能的能力。对我来说“可读”可提高我们将每一行代码与自然语言和思想联系起来的能力,并且可能会加快我们对一行代码的作用的理解,尤其是如果我们不熟练使用该语言时。

正则表达式就是我认为“极其简单”的一个例子。就我个人而言,它“太简单了,太难以理解了”。在这两个极端之间,我有一个平衡的举动,但是正则表达式确实具有我定义的LISP般的简单性:极简,对称,难以置信的灵活性,可靠性等。正则表达式对我而言的问题是它是如此简单以至于它变得如此难以理解,以至于我认为我永远不会流利(我的大脑不能那样工作,我羡慕可以熟练地编写正则表达式代码的人)。

因此,无论如何,这就是我对“简单性”的定义,它完全独立于“可读性”,有时甚至会相互干扰,导致在“语法上更方便”和可读性更大的库或“语法上”之间达到平衡。不便”,可读性差但库较小。我总是发现真正的“理解便利”和真正的“可维护性”优先事项与后者保持一致,强烈倾向于极简主义,即使是以牺牲可读性和更自然的人类语法为代价(但不涉及正则表达式) 。YMMV。


0

总是?-是的

达到正确的简单性水平是一项复杂而艰巨的任务。但始终值得,因为它总是可以提高可读性。我认为关键是深刻理解。简单性是绝对的,由每个“大块”代码的“新概念”度量。一些新概念意味着更简单。

专注于概念密集的区域,并找到“大块”,“概括”或“抽象”的方法。对代码进行几次传递,使其更简单易读。

一个好的抽象是值得的。良好的抽象使此过程更简单-因此更具可读性。


我不禁会认为您的恐吓报价在那里,因为您知道“概念”和“块”本身都是主观的。一个人的单一概念是另一个人的三种不同观念的融合。很难对主观事物进行绝对和客观的衡量。
Steve314 2011年

@ Steve314:吓人报价?否。en.wikipedia.org/wiki/Chunking似乎都相似,因为它们描述了通过分块进行的简化。(除了吉他技术外,似乎有所不同。)之所以有引号,是因为有太多用于抽象,分块,汇总等的替代术语。如果我只选择一个对象,那么抽象与分块就不一样了。引号是为了强调这是值得商de的。然而。显然,它一直都在做,这似乎是人类的自然趋势。
S.Lott

我不能否认的是-分块一直在进行,这是个好主意。我否认的是,对于块之间的边界应该有一个明确的客观规则。我的论点可能有些古怪而不是有用,但我仍然怀疑“始终提高可读性”中的“始终”。
Steve314,2011年

@ Steve314:我们总是抽象,块化和总结以帮助自己理解事物。总是。分块,抽象,总结(“简化”)是我们一直在做的事情。我们就是这样。这就是我们的大脑理解现实的方式。简化总是可以提高可读性,并且可以始终做到。
S.Lott

是的,我们愿意。我从没说过。
Steve314

-2

这些问题使我想起了有关Stack Overflow的答案,尤其是以下引用(用简单的替代质量):

质量-您知道它是什么,但您不知道它是什么。但这是自相矛盾的。但是有些事情比其他的要好,就是说它们具有更高的质量。但是,当您试图说出质量是什么时,除了拥有质量的东西之外,一切都变得很糟糕!没什么好说的。但是,如果您不能说什么是质量,您怎么知道它是什么,或者您怎么知道它甚至存在?如果没有人知道它是什么,那么就所有实际目的而言,它根本不存在。但实际上,它确实存在。这些成绩还基于什么?人们为什么还要为某些事情付出大笔的金钱,而另一些则扔进垃圾桶呢?显然有些事情比其他事情要好-但是``更好''是什么?-这样一遍又一遍 旋转的心理轮子,无处可寻。质量到底是什么?它是什么?

我认为重要的是要牢记,一个编码后的编码标准将充分发挥其优势,不会使优秀的程序员脱颖而出。诸如“简单”之类的词可能由不同的人以不同的方式解释(请参阅艾丹·库利的回答),但这可能不是一件坏事。初级程序员仍然需要让高级程序员审查其代码,并了解为什么高级程序员对“简单”的解释比自己的解释更好。


1
这就是为什么我在问题中定义了简单性的原因。
理查德

-2

每行一个函数调用更简单?让我们尝试一个例子!

=== One call per line ===
x = y.getThis()
a = x.getThat()
b = a.getSomethingElse()

=== Less "simple" version ===
b = y.getThis().getThat().getSomethingElse()

你怎么看?每行一个电话实际上更简单吗?


是。每行一个电话对我来说更简单易读。但是,可读性主观的。(而且,这甚至无法回答这个问题。)
理查德(Richard

嗨,斯蒂芬,您能详细说明一下您如何回答这个问题?目前尚不清楚您要在此处指出什么。

@MarkTrapp没问题。最初的问题提出了一种每行单功能的方法。我发布的示例显示了相同的两个代码段,一个实现为每行单个功能,另一个实现为链式方法调用。我认为链式调用更容易阅读:简短,没有不必要的临时变量且优雅。
斯蒂芬·格罗斯

如果您必须链接吸气剂,则您的代码是精心设计的
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.