无论您多么热爱一种编程语言,其中总是有一些细节并不尽如人意。
在这个问题上,我想特别关注语法元素。在您经常使用的一种编程语言(也许是您最喜欢的编程语言,或者您可能不得不在工作中使用的一种编程语言)中,您发现最不可读,不清楚,不便或不愉快的语法元素是什么?
无论您多么热爱一种编程语言,其中总是有一些细节并不尽如人意。
在这个问题上,我想特别关注语法元素。在您经常使用的一种编程语言(也许是您最喜欢的编程语言,或者您可能不得不在工作中使用的一种编程语言)中,您发现最不可读,不清楚,不便或不愉快的语法元素是什么?
Answers:
我并没有经常被它咬伤,但是这是一个非常糟糕的主意,它使我旋转。
这是规则(来自ECMA-262第7.9节)
例:
return 1; // returns 1
return
1; // returns undefined
/**
* Name of user
*/
private String name;
/**
* Gets name of user
* @return Name of user
*/
public String getName() {
return this.name;
}
/**
* Sets name of user.
* @param name
*/
public void setName(final String name) {
this.name = name;
}
啊!
我有这个问题
public
。成员应该是私有的,并且应该由具有更高层次逻辑的类明智地操纵,而不是使用客户端代码中的getter和setter方法。
Python在这方面惹恼了我。我的意思是,无论如何我都会缩进,但这使我不得不这么做。使表示形式成为语法的一部分让我感到不快。
switch
声明(在C,C ++,C#,Java等)这是为什么我觉得很不方便的示例:
switch (someVariable)
{
case 1:
int i = something();
doSomething(i);
break;
case 2:
int i = somethingElse();
doSomethingElse(i);
break;
}
这不会编译,因为i
在同一作用域中重新声明了变量。这似乎是一个很小的细节,但它确实经常咬我。我可以添加大括号来缓解这种情况,但是如果大括号是语法的必需部分,并且没有多余的缩进级别,那会很好。我也真的很讨厌不得不写多余的东西break
。这样会更好:
switch (someVariable)
case 1
{
int i = something();
doSomething(i);
}
case 2
{
int i = somethingElse();
doSomethingElse(i);
}
default
{
...
}
这使它看起来更像if
/ else
链,这是一件好事,因为它在语义上也相似。至少在C#中,它仍然不是同一回事,因为在一条switch
语句中,大小写标签的顺序无关紧要,而在if
/中else
却是如此。
goto
就像C#中一样,它仅要求将其更明确(使用a 或类似名称)。
case Foo of 1,3: Result := 'odd'; 2,4: Result := 'even' end;
。
break
是默认值,只需要指定fallthrough 。无需记住评论了!:)
我总是忘了,在VB.NET中初始化固定数组时,您是在指定数组的上限,而不是在C / C ++,PHP或Java中指定元素的数量。除了VB6(我们不会去那里...),我想不出另一种这样做的语言:
Dim myArray(20) as Integer '# Creates an array with 21 elements,
'# indexed from 0 to 20
大多数语言都允许您声明一个变量并将其分配给一行代码。另一方面,VB6迫使您使用两个。
Dim i as Integer
i = 0
Dim derpderp as Collection
Set derpderp = new Collection
您可以使用冒号将两个命令放在一行上,但是在实际代码中它很快就会变得混乱。
Dim i as Integer: i = 0
Dim derpderp as Collection: Set derpderp = new Collection
Dim Hurp As New Collection
访问Hurp
时引用了Nothing
,则将在访问之前对其进行神奇的初始化。如果您明确将其设置为Nothing
并再次触摸它,它将复活...喘着粗气!
//
不会像其他许多语言(如PHP和Javascript)那样注释掉代码行。虽然/* this is commented out */
有效,但我更喜欢使用//
。
这很麻烦,因为有一半时间我忘记了编辑CSS,然后不得不返回并修复错误。
PHP具有许多方便的函数,它们几乎可以对数组或字符串进行所有操作。其中许多操作都需要同时使用a $needle
和a $haystack
,但是不同的函数将它们以不同的顺序进行处理。哪个功能需要哪个参数是我的大脑拒绝吸收的那些事实之一,无论我遇到它们有多频繁!
// Check whether $needle occurs in array $haystack
bool in_array (mixed $needle, array $haystack [, bool $strict])
// Check whether $needle is a substring of $haystack
string strstr (string $haystack, mixed $needle [, bool $before_needle=false])
有趣的是,PHP似乎在内部与这些顺序保持一致,因为所有字符串函数似乎都在使用,$haystack, $needle
而数组函数则相反,但是对于刚接触PHP的人来说,这可能需要一些时间。在ExpressionEngine上有一篇很好的文章,更详细地讨论了这个特殊的问题,并且在PHP bug列表中进行了讨论,其中PHP团队的回复很短!
helly@php.net
然后使用一个不错的IDE。
$needle
,$haystack
因为它使人联想起在大海捞针中找针的方式。
strstr
。如此精美,毫无意义。
self
实例方法定义中的参数
self
很容易发生,并且要花费时间。
期。句号 故事结局。
从哪儿开始?哦,我知道从哪里开始:Java异常复杂,丑陋,愚蠢且固有损坏的泛型。需要我多说?:(好的,然后:键入erasure。
然后是不确定的资源管理。脚船夫!
接下来是什么?哦,是的:Java的愚蠢正则表达式是我最讨厌的东西。我无法计算由于没有足够的反斜线而被抽水的次数。这比无法访问本千年以来的任何Unicode属性要糟糕得多,这是完全事实。 十年过时了!!!完全没用。乱扔垃圾
然后是字符类快捷方式不适用于非ASCII的错误。多么皇家的痛苦!并且甚至不要考虑使用\p{javaWhiteSpace}
; 它对几个非常常见的Unicode空白代码点没有做正确的事情。
你知道\p{javaJavaIdentifierStart}
有财产吗?他们在想什么呢?很高兴他们对如此狡猾的偷窥者感到不满。
曾经尝试使用CANON_EQ标志吗?您知道那确实有什么作用吗?所谓的“ Unicode大小写”怎么样?一堆普通的包装盒根本无法使用。
然后,它们使编写可维护的正则表达式变得很困难。Java仍然没有弄清楚如何编写多行字符串,因此您最终会写出如下疯狂的东西:
"(?= ^ [A-Z] [A-Za-z0-9\\-] + $) \n"
+ "(?! ^ .* \n"
+ " (?: ^ \\d+ $ \n"
+ " | ^ [A-Z] - [A-Z] $ \n"
+ " | Invitrogen \n"
+ " | Clontech \n"
+ " | L-L-X-X \n"
+ " | Sarstedt \n"
+ " | Roche \n"
+ " | Beckman \n"
+ " | Bayer \n"
+ " ) # end alternatives \n"
+ ") # end negated lookahead \n"
这些换行符是什么?哦,只是Java的愚蠢。他们使用Perl注释,而不是Java注释(白痴!),直到行尾。因此,如果您不把那些放在\n
那儿,那么就砍掉其余的模式。and和双du!
不要在Java中使用正则表达式:您最终只会想砸东西,这一切都是那么痛苦和破裂。我不敢相信人们会忍受这一点。 有些不。
然后,我们可以开始讨论编码带来的Java白痴废话。首先,事实是,即使Java的字符是Unicode,默认的平台编码始终是一些la脚的8位编码。然后就是他们如何在编码错误时不引发异常。您一定会胡扯。还是这样:
OutputStreamWriter(OutputStream out)
Creates an OutputStreamWriter that uses the default character encoding.
OutputStreamWriter(OutputStream out, Charset cs)
Creates an OutputStreamWriter that uses the given charset.
OutputStreamWriter(OutputStream out, CharsetEncoder enc)
Creates an OutputStreamWriter that uses the given charset encoder.
OutputStreamWriter(OutputStream out, String charsetName)
Creates an OutputStreamWriter that uses the named charset.
有什么不同?您是否知道,如果遇到编码错误,只有其中一个会引发异常?剩下的只是给他们开枪。
然后是Java字符的独特之处还不足以容纳字符!他们到底在想什么?这就是为什么我称它们为charchars。如果您希望它能正常工作,则必须编写如下代码:
private static void say_physical(String s) {
System.out.print("U+");
for (int i = 0; i < s.length(); i++) {
System.out.printf("%X", s.codePointAt(i));
if (s.codePointAt(i) > Character.MAX_VALUE) { i++; } // UG!
if (i+1 < s.length()) { System.out.printf("."); }
}
}
还有谁想这样做?旁边没人。
有多少个字符"\uD83D\uDCA9"
?一个或两个?取决于您如何计算它们。正则表达式引擎当然会处理逻辑字符,因此模式^.$
将成功而模式^..$
将失败。这种疯狂表现在这里:
String { U+61, "\u0061", "a" } =~ /^.$/ => matched.
String { U+61, "\u0061", "a" } =~ /^..$/ => failed.
String { U+61.61, "\u0061\u0061", "aa" } =~ /^.$/ => failed.
String { U+61.61, "\u0061\u0061", "aa" } =~ /^..$/ => matched.
String { U+DF, "\u00DF", "ß" } =~ /^.$/ => matched.
String { U+DF, "\u00DF", "ß" } =~ /^..$/ => failed.
String { U+DF.DF, "\u00DF\u00DF", "ßß" } =~ /^.$/ => failed.
String { U+DF.DF, "\u00DF\u00DF", "ßß" } =~ /^..$/ => matched.
String { U+3C3, "\u03C3", "σ" } =~ /^.$/ => matched.
String { U+3C3, "\u03C3", "σ" } =~ /^..$/ => failed.
String { U+3C3.3C3, "\u03C3\u03C3", "σσ" } =~ /^.$/ => failed.
String { U+3C3.3C3, "\u03C3\u03C3", "σσ" } =~ /^..$/ => matched.
String { U+1F4A9, "\uD83D\uDCA9", "💩" } =~ /^.$/ => matched.
String { U+1F4A9, "\uD83D\uDCA9", "💩" } =~ /^..$/ => failed.
String { U+1F4A9.1F4A9, "\uD83D\uDCA9\uD83D\uDCA9", "💩💩" } =~ /^.$/ => failed.
String { U+1F4A9.1F4A9, "\uD83D\uDCA9\uD83D\uDCA9", "💩💩" } =~ /^..$/ => matched.
这种愚蠢的想法是因为您无法写出完全合理的\u1F4A9
文字,当然也不会收到任何警告,说明您无法做到这一点。它只是做错了事。
笨蛋
当我们这样做时,整个\uXXXX
记号先天性地死了。Java预处理程序(是的,您听说过)比Java更早实现了,所以您被禁止编写诸如之类的完全合理的东西"\u0022"
,因为当Java看到时,它的预处理程序已将它变成"""
,因此您会迷失方向。哦,等等,不,如果它在一个正则表达式!这样就可以使用"\\u0022"
了。
权利!
您知道Java中无法isatty(0)
拨打电话吗?您甚至都不被允许考虑这种想法。这对您不利。
然后是整个类路径可憎。
还是事实是无法在同一源文件中指定Java源文件的编码,这样就不会丢失它?我再次想知道:他们在想什么?
停止疯狂!我不敢相信人们会忍受这种垃圾。这是一个完全的笑话。我宁愿成为沃尔玛的问候者,也不愿忍受残酷的Java精神错乱所带来的麻烦。一切都坏了,他们不仅无法修复,而且无法修复。
这是同样的狡猾,贪婪的人,他们以一种使某项printf()
功能非法的语言感到自豪。e,那肯定效果很好,不是吗??
纯粹的麻木头骨。them子拍对他们来说太客气了。如果我想用汇编器编程,我会的。这不是一种可挽救的语言。皇帝没有衣服。
我们讨厌它。我们永远讨厌它。让它死死!
^H
)组成:)
C和C ++中的函数指针声明语法:
(int)(*f)(int, int);
声明一个名为f
的函数指针,该指针的指针可以为2 int
并返回int
。
我更喜欢这样的语法:
f: (int, int) => int
假设您要声明一个函数指针g
,该指针的pointe可以接受两个int
s,并且函数from int
和int
to 可以取一个int
,然后返回int
。
使用C或C ++表示法,您可以将其声明为:
(int)(*g)(int, int, int(int, int));
使用上述提议的符号,可以将同一事物声明为:
g: (int, int, (int, int) => int) => int
后者是更直观的IMO。
另外:称为OOC的编程语言可修复此语法(以及C和C ++中的其他各种语法问题)。在此处查看其主页。
Java的详细程度。
即:
public static final int
const int
例如。
a+(b*c)/d*e+f/g^20
使用BigInteger
Java 编写。为什么这种语言不允许运算符重载?
该语法不仅丑陋,而且在新开发人员必须考虑字符串中的名称空间时会引起混乱。(PHP在双引号字符串中插入反斜杠作为转义序列。试图以\you\should\never\do\that
双引号字符串而不是单引号字符串表示名称空间将导致换行,制表符和灾难。)
我鄙视以下事实:在if / while / for语句之后,花括号可以是可选的。
特别是当我看到类似的代码时,
if (...)
for(...)
... One line of stuff ...
请放入括号并完成操作。
for (...) while (...) if (...) { x(); y(); }
,最好将其重写为for (...) { while (...) { if (...) { x(); y(); } } }
带有适当缩进的。
与几乎每种明智的语言不同,VBScript使用按位运算符而不是逻辑运算符。实际上这是什么意思?好吧,正如埃里克·利珀特(Eric Lippert)指出的那样:
If Blah = True Then Print "True!" Else Print "False!"
和
If Blah Then Print "True!" Else Print "False!"
在VBScript中不一样!
但是,更糟糕的是,这意味着VBScript中没有短路评估,因此,如果以下语句将使您的程序崩溃Blah
:Nothing
If (Not Blah Is Nothing) And (Blah.Frob = 123) Then
...
没错,即使第一个错误,VBScript也会评估AND比较的两个部分!只是让它陷入...
If x Then ... If Not x Then ... End If ... End If
并发现x为2的那一天,您才真正使用VB / VBScript进行编程。
编辑:在评论中的讨论之后,我决定更新此答案以更好地解释自己。
我真的很讨厌函数指针在C中的显示方式。通常,任何变量声明都看起来像一个元组:type varname;
另一方面,函数指针声明看起来像是函数名称之前带有*的函数声明。我可以接受它作为指针类型的描述,但是在C语言中,它同时声明了类型和该类型的变量的名称。在我看来,这是不一致的,因为类型声明与变量声明是不同的。struct myStruct{int X; int Y;}
仅定义类型,未定义名为的变量myStruct
。同样,我没有看到将类型声明和变量声明分组到函数指针中的一个原子语句中的理由,也看不到与type varname;
结构的偏差。
有人指出这与某些螺旋规则是一致的,也许是这种情况,但是好的语法的标志是它具有自我解释性,并且其内部逻辑是显而易见的。螺旋定律无论如何都不是显而易见的。
typedef
。
我整天都在用期望在每行结尾使用分号的语言进行工作。在VBScript行的末尾添加一个,您的代码将不再运行。
输入/输出参数。我全力支持论证(我是好东西),论证也很好,但是必须传达这两种状态的论点惹恼了我。
我这里的目标是从参数获取输入,然后用某些输出覆盖该输入的函数。可以通过引用传递对象来更新它。但是,大多数情况下,对于原始类型,获取一个对象,使用它,然后完全更改它,对我来说是不合适的。您不应该通过inout 更改参数的含义。
in
的in
论点要么,我只是在谈论它们。而且,在我看来,没有什么可以原谅输入/输出参数,而将其明确表示并不能减轻痛苦。应该没有理由必须使用对函数有帮助的值来初始化局部变量,并具有相同的变量,然后突然包含函数决定放入的任何内容。这两个应该是不同的。总是。
struct
新值取决于值的情况)相同的另一个字段struct
。)
class
C#中的对象实例)由函数修改,对我来说很好。除了我不喜欢非常数结构之外,还可以通过传递struct
带有ref
关键字的a来更新它。我真正讨厌的是输入被输出覆盖。我能想到的最好的例子是Berkeley socket的accept
函数:它需要一个socklen_t*
参数,在输入时必须包含struct sockaddr*
所传递的大小;并且在输出时,将包含已写入其中的字节数。这应该是犯罪的。
通常,变量声明的格式为type variable_name
。您可以轻松地从左到右读取这些声明。但是int foo[size]
首先看起来像是声明foo
为int,然后进一步阅读,发现foo的类型为“整数数组”。 int[size] foo
读起来好多了。
而且我也很讨厌它,当程序员声明这样的指针类似的原因:int *foo
。由于某些原因,我没有弄清楚,这是它编写的典型方式。
int* a, b;
值不一定申报a
和b
为指针; 唯一的a
是。因此,最好将其编写为int *a, b;
(甚至最好将它们编写为两个声明)。
*
,没有绑定到类型。
void(int) *f;
?不:void (*f)(int);
int* a, b;
您提到的问题更为严重。
Java中的冗余参数化:
HashMap<String,HashMap<String,String>> foo = new HashMap<String, HashMap<String, String>>();
编译器认为 foo
还有什么其他类型的参数化?
HashMap<String,HashMap<String,String>> foo = Maps.newHashMap();
HashMap<String,HashMap<String,String>> foo = new HashMap<>();
What other type parameterization does the compiler think foo could have?
-无论出于何种原因,它都是原始类型-好像任何理智的人甚至会混合使用原始类型和参数化类型。
由于人们已经对=
vs 提出了抱怨==
,让我指出一个更糟糕的选择。PL / I同时拥有:=
和=
,但是当某项“显然”是一项任务时,它将让您摆脱使用=
它的麻烦。:=
在编译器将其解释为比较的情况下,使用let可以强制将某些内容作为赋值。
不幸的是,编译器并非总是以您期望的方式做出决定。仅考虑一个明显的例子:
A = B = 0;
现在,对于大多数熟悉大多数“普通”语言的人来说,这的含义非常明显-为A和B都分配0。PL/I只是有点...不同。由于只有(疯狂的)语言设计者才知道的原因,第一个=
解释为赋值,而第二个=
解释为比较。因此,这会将B比较为0,然后将该比较结果分配给A(遵循C样式约定,即“ false”导致0,“ true”导致1)。
因此,如果B为0,则A变为1。否则,A变为0。换句话说,这实际上不是在给A和B分配相同的值,而是确保A 不能具有与B相同的值。
底线:尽管起初C / C ++ / PHP风格看起来很痛苦,但替代方案要糟糕得多1。
1好吧,从技术上讲,还有另一种选择:Pascal样式,=
始终表示比较和赋值总是需要的:=
。使用一段时间后,很明显(至少对我而言),分配比比较要普遍得多,如果您需要额外的“材料”来消除二者的歧义,则一定要保持分配的简洁明了,比较时需要额外的“垃圾”,反之亦然。
==
用于平等和:=
分配。这样,您可以输入更多信息,但是避免孤独=
有助于避免错误。
if($x < 10) do_something();
。目前,您必须将其写为do_something() if($x < 10);
或if($x < 10) { do_something(); }
。do_something() if ($x < 10);
还不错
if(..) ...
启动更干净,因为您可以使所有表达式在右边对齐。但是有时候我真的很想说if($x < 10) do_something();
,不幸的是Perl不允许我这样做。
if ($x<10) do_something(); and_call_me();
然后又想知道为什么从未接到电话。我希望C和家人需要使用大括号以防止发生此类错误。
else
。(我希望它EXPR1 if COND else EXPR2
像Python一样)
reinterpret_cast<unsigned long>
在C ++中。此操作对于处理外部API并确保数值精度很有用,为什么键入起来这么麻烦却看起来那么麻烦?
reinterpret_cast
在C ++中使用a 是很强烈的代码味道。99%的时间,当您要使用它时,您很有可能不需要-您可能做错了什么。其他1%的时间是您与C进行接口。reinterpret_cast
如果不能,它将破坏类型系统以使其正常工作。这通常是一件坏事。
遍历数组时,JavaScript中的for ... in构造和PHP中的foreach构造。与正确的代码相比,它们使编写错误更容易。
C / C ++中的数组或数组的指针。我仍然对这些感到困惑。
int[]*
是一个指向整数int*[]
数组的指针,并且是一个指向整数指针的数组。同样适用于const
s:int* const
是整数的常量指针,而const int*
and int const*
是指向整数(或整数常量)的指针。
int (*p)[10];
声明p
是一个指向10个整数的数组的指针。如果sizeof (int) == 4
,然后p++
将提前p
通过40
Javascript / Java等,等于比较,例如if(a == 1)
我应该写几次if(a = 1)?
作为一个人,我读得很完美。但是织补解释器/编译器说:“嘿,我给a赋值1,然后检查a是否等于1,您是否相信它是对的!
把我逼上墙。
if(a == 1)的可读性要差得多,无论如何解释器/编译器应该知道我的意思;数百年来,许多其他次要语言(VB)一直在成功地进行开发。
if (a = true)
意思?
if (a == true)
,只写if (a)
。如果您的意思是a = true
,那么该if
语句是多余的。
structPointer->member
在C / C ++中。可能适合阅读别人的代码,但我不喜欢它。两个字符而不是一个...太浪费空间了!
.
,后者适用于非指针对象的成员。模棱两可的语法不好,mmmkay?
shared_ptr
,其中->
访问所包含的类型并.
访问其shared_ptr
自身的属性?抱歉,我在这里完全不同意。
->
来访问任何对象。
括号中的Scala多行代码
例如:
class Foo(
val bar: String,
val baz: Int,
val bing: String,
val bong: Boolean
) extends Model {
// body here
}
您实际上从中得到的是很棒的。它为您生成构造函数以及getter和setter。但是它确实很丑陋,打破了我所有关于如何缩进代码的思维模式,并且基本上让我感到自己像是在一个怪异的三明治中,一方面是Java,另一方面是Lisp。(哦,等等……这就是Scala的重点。)
val bar
依此类推)还是花括号中的部分(紧随其后的部分extends Model
)?(该代码中没有方括号)