五大受损, 全面解析PHP的糟糕设计 已翻译 100%

tsl0922 投递于 2012/11/27 15:54 (共 26 段, 翻译完成于 11-27)
阅读 3699
收藏 7
PHP
0
加载中

前言

    我的脾气古怪. 我会抱怨很多东西. 这个星球上大多数技术我都不喜欢.

    PHP不仅使用起来尴尬, 还有要嘛我想要的不适合, 要嘛不是最令人满意, 要嘛违背我的信仰. 我可以告诉你关于一门语言, 所有我想避免的好方式, 所有我喜欢的坏方式. 来吧, 问吧! 谈话会很有趣!

    php是唯一的例外. 几乎php抽象的所有东西都是支离破碎的. 包括语言, 框架, 整个生态系统都一塌糊涂. 我几乎不能单独列出咒骂的事情, 因为它全身都坏了. 每次我打算编辑一堆杂乱如麻的php抱怨清单的时候, 我都被一些琐事打乱, 越深入就越会发现其它令人震惊的事情.

    php让人难堪. 它是如此的破碎, 但那些被培训的业余爱好者, 却对它称赞不已. php在做一些徽不足道的挽回措施, 但我选择忘记它.

    不过我得让我的系统摆脱这些东西, 也就这样了, 这是最后一次尝试.

tsl0922
翻译于 2012/11/27 15:55
1

打个比喻

我只是随口和 Mel 抱怨下, 而她却坚决让我发表出来.

    我甚至说不出来PHP到底怎么了, 因为 -- 还好. 想想你有一个, 嗯, 工具箱吧. 一堆工具. 看起来还好, 有标准的东西.

    你拔除螺丝钉, 它怪异的有三个头.  OK, 好吧, 这对你不太有用, 但你猜迟早有天会有用.

    你拿出榔头, 被震住了, 两边都有是尖爪. 但它仍然能用, 我的意思是, 你可以用两头的中部斜着敲.

    你拿出老虎钳, 但它们没有锯齿面. 表面平而光滑. 这没多大用, 但依然能用, 没什么.

    你可以继续. 工具箱的东西都是怪异和琢磨不定的, 但又不能说毫无价值. 整体看没什么大问题; 它的工具都齐全.

    现在, 想象有很多使用这些工具的木匠, 它们和你说:"这些工具有什么问题呢? 我们都用过, 它们工作都很好啊!". 工匠们给你展示他们建的房子,每个门都是五边形的而屋顶是癫倒的. 你敲前门, 它向内倒榻了, 而他们却抱怨你打破了他们的门.

    这就是PHP的问题. 

tsl0922
翻译于 2012/11/27 15:55
1

立场

我认为下面的特质对于一门语言的生产力和可用性是重要的, 而PHP在大范围破坏它们. 如果你不同意这些, 好吧, 我无法想像, 我们永远不会达成一致.

    >>    一门语言必须是可预见的. 它是將人类的思想反映给计算机执行的媒介, 因此它的关键是, 人类对程序的理解实际要正确.

    >>    语言必须一致. 相似的东西就要看起来相似, 不同的就是不同. 学习了语言的部分知识, 就应能很容易理解剩下的部分.

    >>    语言必须简洁. 新语言应该减少继承旧语言的不好的形式. (我们也可以写机器码.) 新语言当然应努力避免织入新的特有的形式.

    >>    语言必须是可靠的. 语言是解决问题的工具; 应尽量避免引入新问题. 任何"陷阱"都会大量的分散注意力.

    >>    语言必须是可调试的. 当出错的时候, 程序员必须修正它, 我们需要获得我们想要的帮助.

我的立场是:

    >>    PHP到处处充满惊奇: mysql_real_escape_string, E_ACTUALLY_ALL

    >>    PHP不一致: strpos, str_rot13

    >>    PHP需要特别形式: error-checking around C API calls, ===

    >>    PHP古怪: ==. for($foo as &$bar)

    >>    PHP晦涩: 默认无栈跟踪或fatals, 复杂的错误报告

我不能就单个问题解释为什么它归为这些类, 否则將会没完没了. 我相信读者自己会思考.

tsl0922
翻译于 2012/11/27 15:56
1

不要再和我扯这些东西了

我知道很多有利的论点. 我也听到很多反驳的论点. 这些都只能让谈话立即停止. 不要再跟我扯这些东西了, 求你了. :(

    >>    不要和我说"好的开发者能用任何语言写出好的代码", 或者坏开发者.. 吧啦吧啦. 这毫无意义. 好的工匠可以用石头或锤子驾驭钉子, 但你见过有多少工匠用石头的? 成为一个好开发者的标准之一就是善于选择工具.

    >>    不要和我说熟记上千个例外和古怪行为是开发者的职责. 是的, 这在任何系统中都是必要的, 因为电脑是傻的. 这不意味着, 系统能疯狂的接受而没有上限. PHP有的只是异常, 这是不行的, 一旦和语言摔角决斗, 你实际编写程序就要花费更多的努力. 我的工具不能为我创建应用产生积极作用.

    >>    不要和我说 "那就是C API 的工作方式". 这星球上高级语言存在的目的是什么, 它们能提供的一切仅仅是一些字符串助手函数和一堆C的包装器? 如果是这样, 那就用C! 这里, 甚至还有为它准备的CGI库.

    >>    不要和我扯 "搞出奇怪的事, 是你活该". 如果存在两个特性, 总有一天, 某些人会找到一起使用它们的理由. 再次强调, 这不是C; 这里没有规范, 这里不需要 "未定义行为".

    >>    不要再和我扯 Facebook 和 Wikipedia 就用的PHP.  我早知道了! 它们也能用 Brainfuck 写, 但只要他们足够陪明, 不断折腾这些事情, 他们总能克服平台的问题. 众所周知, 如果使用其它语言编写, 开发时间可能会减少一半或加倍; 单独拿出这些数据毫无意义.

上帝保佑, 不要再和我扯任何东西了!  如果列出的没有伤害你的PHP的观点, 无所谓, 因此请停止在网上做无意义的争论, 继续开发高帅富酷的站点来证明我是错的 :).

偷偷告诉你: 我非常喜欢Python. 我也很乐意对它说些你不爱听的话, 如果你真想的话. 我并不要求它完美; 我只是想扬长避短, 总结我想要的最佳东西.

tsl0922
翻译于 2012/11/27 15:56
1

PHP

语言核心

CPAN被称为 "Perl的标准库". 这并没有对Perl的标准库做过多说明, 但它蕴含了健壮的核心可以构建强大的东西的思想.

基本原则

PHP最初很明确的是为非程序员设计的(言外之意,  非专业程序); 根源已经很难脱离. 从PHP 2.0 文档中挑选出来的对话:

    一旦你开始为每个类型区分不同的操作符, 你就开始使用语言变得复杂了. 例如, 你不能为strings使用 '==', 你现在必须用 'eq'. 我没看出这点来, 特别是那些类似PHP的脚本语言, 它们大多数相当简单而多数情况下, 作为非程序员, 只想要一门包含少量基本逻辑语法的语言, 而不想付出过多学习曲线.

    >>    PHP 为保持前进不惜代价. 什么都有比没有好.

    >>    这不是个正确的设计原则. 早期的PHP受Perl影响; 大量的标准库参考C使用 "out" 参数; OO部分的设计像C++和Java.

    >>    PHP从其它语言中引入大量的灵感, 但对那些熟知其它语言的人, 仍然难以理解. (int)看起来像 C, 但是 int 并不存在. 命名空间使用 \. 新的数组语法使用 [key => value], 不同于任何其它语言定义hash字面量的形式.

    >>    弱类型(例如, 默默的自动在 strings/mumbers/等间转换)是如此的复杂.

    >>    少量的新特性以新语法实现; 大多数工作通过函数或者看起来像函数的东西完成. 除了类的支持, 这理所当然的需要新的操作符和关键字.

    >>    本页列出的问题都有官方解决方案 -- 如果你想资助 Zend 修复它们的开源编程语言的话. 

    >>    路漫漫, 其修远. 思考下面的代码, 从PHP文档的某地方挑出来的. 

1 @fopen('http://example.com/not-existing-file', 'r');

    它將做什么? 

        >>    如果PHP使用 --disable-url-fopen-wrapper编译, 它將不工作. (文档没有说, "不工作"是什么意思; 返回 null, 抛出异常?)

        >>    注意这点已在 PHP 5.2.5 中移除.

        >>    如果 allow_url_fopen 在 php.ini 中禁用, 也將不工作. (为什么? 无从得知.)

        >>    由于 @ , non-existent file 的警告將不打印.

        >>    但如果在php.ini中设置了scream.enabled, 它又將打印.

        >>    或者如果用 ini_set 手动设置 scream.enabled. 

        >>    但, 如果 error_reporting 级别没设置, 又不同.

        >>    如果打印出来了, 精确去向依赖于 display_errors , 再一次还是在 php.ini. 或者 ini_set中.

    我无法告诉你这个函数调用的行为, 如果没有查看编译时标志 , 服务器端配置, 和我的程序中的配置的话. 这些都是内建行为.

    >>    该语言充满了全局和隐似状态. mbstring 使用全局字符编码. func_get_arg 之类的看起来像正常的函数, 但是只对当前正在执行的函数操作. Error/exception 处理默认是全局的. register_tick_function 设置了一个全局函数去运行每个 tick(钩子?) ---- 什么?!

    >>    没有任何线程支持. (不奇怪, 因为上面已给出.) 加之缺乏内建的 fork (下面提到), 使得并行编程极其困难.

    >>    PHP的某些部分在实践中会产生错误代码.

        >>    json_decode 对不正确的输入返回 null,  尽管 null 也是一个 JSON 解码的合法对象 -- 该函数极不可靠, 除非你每次使用后都调用 json_last_error. 

        >>    如果在位置0处找到, array_search , strpos, 和其它类似的函数返回0, 但如果都没有找到的话. 会返回 false

让我们稍稍展开最后一部分.

    在C中, 函数如 strpos 返回 -1, 如果未找到. 如果你没检查这种情况, 却试着以下标使用它, 那將可能命中垃圾内存, 程序会崩溃. (也许吧, 这是C. 谁泥马知道. 我确定至少有工具处理它)

    话说, Python中, 等效的 .index 方法將抛出一个异常, 如果元素没找到的话. 如果你不检查该情形, 程序將崩溃.

    在PHP中, 该函数返回 false. 如果你把 FALSE 作为下标使用, 或者用它做其他事情, PHP会默默的將它转成0, 但除了用于 === 比较. 程序是不会崩溃的; 它將执行错误的逻辑, 且无任何警告, 除非你记得在每个使用 strpos 和其它类似函数的地方包含正确的样版处理代码.

    这真是糟透了! 编程语言只是工具; 它们是为我服务的. 这里, PHP给我布下了陷阱, 等着我跳进去, 而我不得不时刻警惕这些无聊的字符串操作和相等比较. PHP是个雷区.

我已经听过很多关于PHP解析器的故事, 它的开发者来自世界各地. 有从事PHP核心开发工作的人, 有调试PHP核心的人, 也有和核心开发者交流过的人. 没有一个故事是赞赏的.

因此不得不在这里插入一句, 因为它值得重复: PHP是个业余爱好者的社区. 极少数人设计, 为它工作, 或极少有人知道他们在做什么. (哦, 亲爱的读者, 你当然是个极品例外!) 那些成长了, 想转投其它平台的人, 使整个社区的平均水平下降. 这个, 就是这里, 是PHP的最大问题: 绝对的盲目领导盲目.

好了, 回来面对现实吧.

tsl0922
翻译于 2012/11/27 15:56
1

操作符

     == 不中用.

        >>    "foo" == TRUE , 和 "foo" == 0... 但, 当然 TRUE != 0.

        >>    == 会將两边转成数字, 如果可能的话,  这意味着它將转成 floats 如果可能. 所以大的16进制字符串(如, password hashes) 可能偶然会比较成 true , 尽管它们不一样. 就连 JavaScript 都不会这样做.

        >>    由于某些原因, "6" == "6", "4.2" == "4.20", 和 "133" == "0133". 但注意 133 != 0133, 因为 0133 是八进制的.

        >>    === 比较值和类型... 除了对象, 只有两边实际上是同一对象才为 true ! 对于对象, == 比较值(或每个属性)和类型, 这又是 === 比较任何非对象类型的行为. 好玩吗?

    比较大小也好不到哪去.

        >>    甚至行为都不一致: NULL < -1, 而 NULL == 0. 排序也因此不确定; 它依赖于在排序中比较元素的算法的顺序.

        >>    比较操作符尝试排序数组, 以两种不同的方式: 首先按长度, 然后按元素. 如果它们有相同数量的元素但不同的keys, 它们是不可比的.

        >>    对象比较比其它比较做得更多... 除了那些即不小于也不大于的对象.

        >>    为了类型更安全的 == 比较, 我们有 ===. 为了类型更安全的 < 比较, 我们有... 什么也没有. "123" < "0124", 通常, 不管你怎么做. 类型转换也无济于事.

    >>    尽管上面的举动很疯狂, 但却明确拒绝Perl's的字符串 paris 和算术运行符, PHP没有重载 +. + 就是通常的 +, 而 . 是通常的连接符.

    >>    [] 下标操作符也可以拼写成 {}.

    >>    [] 可以用于任何变量, 不光是字符串和数组. 它返回 null , 无错误警告.

    >>    [] 仅能获取单个元素.

    >>    foo()[0] 是个语法错误. (已在 PHP 5.4 中修复)

    >>    不像(从字面上看)任何其它语言都有的类似的操作符, ?: 是左结合的. 因此:

$arg = 'T';
  $vehicle = ( ( $arg == 'B' ) ? 'bus' :
               ( $arg == 'A' ) ? 'airplane' :
               ( $arg == 'T' ) ? 'train' :
               ( $arg == 'C' ) ? 'car' :
               ( $arg == 'H' ) ? 'horse' :
               'feet' );
  echo $vehicle;

打印 horse.

tsl0922
翻译于 2012/11/27 15:57
1

变量

>>    无法声明变量. 当第一次使用时, 不存在的变量会被创建为 null 值.

>>    全局变量在使用前, 需要 global 声明. 这是根据上面得出的自然结果, 因此这是个完美的理由, 但, 如果没有显示的声明, 全局变量甚至无法读取 -- PHP 將悄悄的创建一个局部同名变量取代它. 我还没见过其它语言使用类似的方法处理范围问题.

>>    没有引用. PHP所谓的引用是个真正的别名; 这无疑是一种倒退, 不像 Perl 的引用, 也没有像 Python 那样的对象标识传递.

>>    没有明显的方式检测和取消引用.

>>    "引用" 使变量在语言中与众不同. PHP 是动态类型的, 因此变量通常无类型... 除了引用, 它修饰函数定义, 变量语法, 和赋值. 一旦变量被引用(可在任何地方发生), 它就一直是个引用. 没有明显的方法探测和解引用需要的变量值.

>>    好吧, 我说谎了. 有些"SPL types" 也作用于变量: $x = new SplBool(true); $x = "foo"; 將失败. 这有点像静态类型, 自己看看.

>>    A reference can be taken to a key that doesn’t exist within an undefined variable (which becomes an array). Using a non-existent array normally issues a notice, but this does not.

>>    通过函数定义的常量称为 taking a string; 这之前, 它们不存在. (这可能实际上是复制 Perl 使用常量的行为.)

>>    变量名是大小写敏感的. 函数和类名不是. 使得方法使用驼峰式命名会很奇怪. 

tsl0922
翻译于 2012/11/27 15:58
1

结构

>>    array() 和几个类似的结构不是函数.  $func = "array"; $func(); 不工作.

>>    数组拆包可以使用 list($a,$b) = .... 操作完成. list() 是类函数语法, 就像数组那样. 我不知道为什么不给一个真正的专用语法, 也不知道为什么名字如些的让人迷惑.

>>    (int) 很显然的被设计成类似C, 但它不是单独的标记; 在语言中, 没有东西被称为 int. 试试看: var_dump(int)不工作, 它会抛出一个解析错误, 因为参数看起来像是强制转操作符.

>>    (integer) 是 (int) 的别名. 也有 (bool)/(boolean)和(float)/(double)/(real).

>>    有个(array)操作符用来转成数组和 (object) 用来转成对象. 这听起来很贴心, 但常常有个用例: 你可以用 (array) 使得某个函数参数, 既可以是单个元素,也可以是列表, 相同对待. 但这样做不可靠, 因为如果某人传递了单个对象,把它转换成数组將实际上生成了一个包含对象属性的数组. (转换成对象执行了反转操作.)

>>    include()这类的函数基本上就是C的#include: 他们將其它的文件源码转存到你的文件中. 没有模块系统, 甚至对 PHP 代码也一样.

>>    没有类似嵌套或者局部范围的函数或类. 它们都是全局的. include 某文件, 它的变量导入到当前函数范围中(给了文件访问你的变量的能力), 但是函数和类存入全局范围中. 

>>    追加数组使用 $foo[] = $bar.

>>    echo 不是函数.

>>    empty($var) 是如此极端, 对于任何其它东西不表现为函数, 除了变量, e.g. empty($var || $var2), 是个解析错误. 为什么地球上有这种东西, 解析器为什么需要了解 empty ?

>>    还有些冗余的语法块: if (...): ... endif;, 等等.

tsl0922
翻译于 2012/11/27 15:58
1

错误处理 

>>    PHP 的一个独特操作符是 @ (实际上从DOS借用过来的), 它隐藏错误.

>>    PHP 错误不提供栈轨迹. 你不得不安装一个处理器生成它们. (但 fatal errors不行 -- 见下文.)

>>    PHP 的解析错误通常只抛出解析的状态, 没其它东西了, 使得调试很糟糕.

>>    PHP 的解析器所指的例如.  ::  内部作为 T_PAAMAYIM_NEKUDOTAYIM, 而 << 操作符作为 T_SL. 我说 "内部的", 但像上面说的, 给程序员显示的 :: 或 << 出现在了错误的位置. 

>>    大多数错误处理打印给服务器日志打印一行错误日志, 没人看到而一直进行.

>>    E_STRICT看起来像那么回事, 但它实际上没多少保护, 没有文档显示它实际上是做什么的.

>>    E_ALL包含了所有的错误类别 -- 除了 E_STRICT.

>>    关于什么允许而什么不允许是古怪而不一致的. 我不知道 E_STRICT 是怎样适用于这里的, 但这些却是正确的:

        >>    试图访问不存在的对象属性, 如, $foo->x. (warning)

        >>    使用变量做为函数名, 或者变量名, 或者类名. (silent)

        >>    试图使用未定义常量. (notice)

        >>    试图访问非对象类型的属性.(notice)

        >>    试图使用不存在的变量名.(notice)

        >>    2 < "foo" (隐藏)

        >>    foreach (2 as $foo); (warning)

而下面这些不行:

        >>    试图访问不存在的类常量, 如 $foo::x. (fatal error)

        >>    使用字符串常量作为函数名, 或变量名, 或类名. (parse error)

        >>    试图调用一个示定义函数. (fatal error)

        >>    Leaving off a semicolon on the last statement in a block or file. (parse error)

        >>    使用 list 和其它准内建宏作为方法名. (parse error)

        >>    用下标访问函数的返回值, 如: foo()[0]. (parse error; 已在 5.4 中修复)

    在列表的其他地方也有几个关于其它怪异解析错误的好例子

>>    __toString 方法不能抛出异常. 如果你尝试, PHP 將 ... 呃, 抛出一个异常. (实际上是个 fatal error, 可以被通过的, 除了...)

>>   PHP 错误和 PHP 异常是完全不同的物种. 它们不能相互作用.

        >>    PHP 错误 (内部, 称为 trigger_error)不能被 try/catch 捕获.

        >>    同样, 异常不能通过 set_error_handler 安装的错误处理器触发错误.

        >>    作为替代, 有一个单独的 set_exception_handler 可以处理未捕获的异常, 因为用 try 块包装你程序入口在         mod_pho 模块中是不可能的.

        >>    Fatal 错误 (例如, new ClassDoesntExist()) 不能被任何东西捕获. 大量的完全无害的操作会抛出 fatal 错误, 由 于一些有争议的原因被迫终结你的程序. 关闭函数仍然运行, 但它们无法获取栈轨迹(它们运行在上层), 它们很难告知该程序是由一个错误还是程序的正常运行结束.

>>    没有 finally 结构, 使得包装代码 (注册处理器, 运行代码, 注销处理器; monkeypatch, 运行测试, unmonkeypatch) 很难看, 很难写. 尽管 OO 和异常大量的复制了Java的模式, 这是故意的, 因为 finally "在PHP上下文中, 只得其形不得其神".Huh ?

tsl0922
翻译于 2012/11/27 15:58
1

函数

>>    函数调用似乎相当昂贵.

>>    一些内建函数与 reference-returning 函数交互, 呃, 一种奇怪的方式.

>>    正如在别处提到的, 很多看起来像函数或者看起来它们应该是函数的东西实际上是语言的构成部分, 因此无法像正常函数一样的工作.

>>    函数参数可以具有 "类型提示", 基本上只是静态类型. 你不能要求某个参数是 int 或是 string 或是 对象 或其它 "核心" 类型, 即使每个内建函数使用这种类型, 可能因为 int 在PHP中不是个东西吧. (查看上面关于 (int) 的讨论). 你也不能使用特殊的被大量内建函数使用的伪类型装饰: mixed, number, or callback.

>>    因此, 下面:

1 function foo(string $s) {}
2  
3 foo("hello world");

    产生错误 the error:

       PHP Catchable fatal error:  Argument 1 passed to foo() must be an instance of string, string given,         called in...

        >>    你可能会注意到 "类型提示" 实际上并不存在; 在程序中没有 string 类. 如果你试图使用         ReflectionParameter::getClass() 动态测试类型提示, 將会得到类型不存在, 使得实际上不可能取得该类型名.

       >>     函数的返回值不能被推断

>>    將当前函数的参数传给另一个函数 (分派, 不罕见) 通过 call_user_func_array('other_function', func_get_args())完成. 但 func_get_args 在运行时抛出一个 fatal 错误, 抱怨它不能作为函数参数. 为什么为什么这是个类型错误? ( 已在 PHP 5.3 中修复)

>>    闭包需要显示的命名每个变量为 closed-over. 为什么解析器不想办法解决? (Okay, it’s because using a variable ever, at all, creates it unless explicitly told otherwise.)

>>    Closed-over 变量, 通过和其它函数参数相同的语义"传递". 这样的话, 数组和字符串等等, 將以传值方式传给闭包. 除非使用 &.

>>    因为闭包变量会自动传递参数, 没有嵌套范围, 闭包不能指向私有方法, 不管是否定义在类中. ( 可能在 5.4 中修复? 不清楚.)

>>    函数没有命名参数. 实际上被 devs 显示拒绝, 因为它 "会导致代码臭味".

>>    Function arguments with defaults can appear before function arguments without, even though the documentation points out that this is both weird and useless. (So why allow it?)

>>    向函数传递额外的参数会被忽略 (除了内建函数, 会抛出异常). 丢失的参数被假定为 null.

>>    "可变" 函数需要 func_num_args, func_get_arg, 和 func_get_args. 这类事情没有语法.

tsl0922
翻译于 2012/11/27 15:58
1
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
加载中

评论(8)

MuskIron
MuskIron
有人爱,有人恨。和python也一样,就想榴莲一下,喜欢的非常喜欢,不喜欢的闻气味都不能闻
放牛娃1988
放牛娃1988
五大受损是什么鬼?
手握华为赛神仙
手握华为赛神仙
用了七八年PHP,我一直都觉得PHP需要被彻底地重构一下,就像PY3吧一些变态的语法改掉。

作者说出了很多我想吐槽的地方,最反感的就是“哈希表用=>”、稀奇古怪的OOP,不支持强类型,神一般的字符串连接符——小数点(当JS和PHP共用的时候这会是个灾难……),反人类的命名空间界定符——反斜杠……
f
forwardlw
黑的好专业,全是英文,不看标题和评论不知道在说什么玩意
C_Guy
C_Guy

引用来自“pangee”的评论

这作者专黑PHP呐。。。

这黑得够专业啊
pangee
pangee
这作者专黑PHP呐。。。
不必在乎朕是谁
不必在乎朕是谁
大快人心!人生苦短,我用 Python!
赵云30
赵云30
真没意思,我本来还希望学点php的东西,结果通篇都是在说坏话,教人去学Python...
返回顶部
顶部