LINQ和Lambda表达式的使用会导致代码可读性降低吗?[关闭]


43

我正在与Linq的同事讨论,我将在此处复制:

同事:让我们在这里诚实。Linq语法很烂。这是令人困惑和不直观的。

我:哦,比T-SQL更令人困惑吗?

同事:嗯,是的。

我:它具有相同的基本部分,选择,位置和来源

同事:对我来说,Linq是关系+ OO的混蛋。同事:不要误会我的意思-它功能强大,但是他们将SQL重新定位为使用agains对象集合。

我认为使用Linq + Lamda的功能非常强大(他同意),并且还使代码更易于阅读(他不同意这一点):

pickFiles = from f in pickFolder.GetFiles("*.txt")
where ValidAuditFileName.IsMatch(f.Name)
select f;

要么

var existing = from s in ActiveRecordLinq.AsQueryable<ScannedEntity>()
where s.FileName == f.FullName && s.DocumentType != "Unknown"
select s;

或(此处为VB代码)

   Dim notVerified = From image In images.AsParallel
     Group Join verifyFile In verifyFolder.GetFiles("*.vfy").AsParallel.Where(
      Function(v) v.Length > 0
      ).AsParallel
   On image.Name.Replace(image.Extension, ".vfy") Equals verifyFile.Name
     Into verifyList = Group
    From verify In verifyList.DefaultIfEmpty
    Where verify Is Nothing
    Select verify

对我来说,这是干净且容易理解的(至少比其他方法更容易阅读),您对此有何看法?


11
一般来说,人类讨厌改变。很大一部分人讨厌它,以至于他们实际上对此感到恐惧。
托尼2010年

9
让我们面对现实吧!linq只是添加到C#和VB.Net的精简函数式编程语法。抨击linq和lambda表达式基本上是说“ FP很烂”。那就是你的同事所说的。我认为辩论已经在其他地方进行了讨论。
Scott Whitlock 2010年

48
人们在真正表示“熟悉”时倾向于使用“直觉”这个词是否会打扰其他人?
拉里·科尔曼

7
无论如何,C#团队(包括Eric Lippert)竭尽全力地解释说Linq 不是 SQL的移植,它像大多数其他功能一样是从头开始设计的。我不得不说您的同事是路德主义者。
亚罗诺(Aaronaught)2010年

16
对于它的价值:我刚做了我的妻子(办公室管理员-接零的实际编程经验),看看aaronaught的3个例子,是无法解密意图的LINQ和拉姆达的例子远远更容易比传统势在必行例子。
史蒂文·埃弗斯

Answers:


73

我再也找不到合适的帖子了,但是Eric Lippert(可能还有其他一些软件专家)曾多次就Linq是如何声明性的问题提出意见的,对于某些类型的问题,Linq 比命令式语法直观得多。

Linq使您能够编写表达意图而不是机制的代码

你告诉我哪个更容易阅读。这个:

IEnumerable<Customer> GetVipCustomers(IEnumerable<Customer> source)
{
    List<Customer> results = new List<Customer>();
    foreach (Customer c in source)
    {
        if (c.FirstName == "Aaron")
        {
            results.Add(c);
        }
    }
    results.Sort(new LastNameComparer());
    return results;
}

class LastNameComparer : IComparer<Customer>
{
    public int Compare(Customer a, Customer b)
    {
        return x.LastName.CompareTo(b.LastName);
    }
}

或这个?

IEnumerable<Customer> GetVipCustomers(IEnumerable<Customer> source)
{
    return from c in source
           where c.FirstName == "Aaron"
           orderby c.LastName
           select c;
}

甚至这个?

IEnumerable<Customer> GetVipCustomers(IEnumerable<Customer> source)
{
    return source.Where(c => c.FirstName == "Aaron").OrderBy(c => c.LastName);
}

第一个示例只是一堆毫无意义的样板,以便获得最简单的结果。任何认为它比Linq版本更具可读性的人都需要检查自己的头部。不仅如此,而且第一个浪费内存。yield return由于排序,您甚至不能使用编写它。

您的同事可以说他想要什么;我个人认为Linq极大地提高了我的代码可读性。

Linq也没有任何“关系”。它可能与SQL有一些表面上的相似之处,但它并未尝试以任何形式或形式来实现关系演算。它只是一堆扩展,使查询和投影序列更加容易。“查询”并不表示“关系”,实际上有几个使用SQL式语法的非关系数据库。Linq 纯粹是面向对象的,由于C#团队对表达式树的伏都教和巧妙的设计,使得匿名函数可以隐式转换为表达式树,因此它恰好通过诸如Linq to SQL之类的框架与关系数据库一起工作。


6
+1如果您不喜欢LINQ,可能是因为“您做的不对” :)
Darknight

1
Linq is purely object-oriented为此+1。这也是我们的团队风格指南在查询语法上使用流利语法的原因。我发现这使得链接的OO性质对于喜欢使用C语言编写对象表示法的人来说更加明显。
SBI

1
我知道这个职位已经7岁了。但这是一个问题:在遇到Linq之前,您是否使用功能语言作为主要工具?
Sergey.quixoticaxis.Ivanov '16

4
坦率地说,我更喜欢使用foor循环,因为这样我马上就可以在哪里可以并且将会有NULL引用异常,如何处理null以及不延迟执行这使得调试变得很困难,并且它不会创建n个列表同样,for循环也更加有效。
困惑

1
@Aaronaught,如果您删除排序并以Horstmann风格重新格式化程序代码,我将说它比LINQ版本更具可读性。而且,根据单一责任原则,排序实际上并不属于GetVipCustomers(),顾名思义,排序只能以任意顺序返回VIP客户的集合。对于顺序很重要的罕见情况(例如输出到屏幕),请调用方对集合进行排序。
Ant_222 '17

69

同事:让我们在这里诚实。Linq语法很烂。这是令人困惑和不直观的。

您不能与这种批评争论。对于您的同事而言,这很糟糕。对于他们来说,我们无法设计一种清晰直观的语法。那是我们的失败,您可以向您的同事道歉。我很乐意就如何改善它提出建议。您的同事特别感到什么困惑或不直观?

但是,您不能取悦所有人。我个人的看法以及与之交谈的大多数人的看法是,查询理解语法比等效的命令式语法要清晰得多。显然,并非所有人都同意,但是幸运的是,在进行语言设计时,我们并不需要所有数百万客户的共识。

关于什么是“直觉”,我想起了英语语言学家的故事,他学习了许多不同的语言,并最终得出结论,英语是所有语言中最好的,因为在英语中,单词的顺序与您所用的顺序相同。想他们与法语不同,他们经常说“狗白吃肉红”之类的话。法国人必须以正确的顺序思考这些单词,然后必须以法语的顺序这些单词,这是多么困难!法语太直觉了!法国人能说出来真是太神奇了。还有德国人?他们以为“狗吃肉”,然后不得不说“狗吃肉”!

通常,“直觉”仅仅是一个熟悉的问题。在停止使用“ select”子句开始查询之前,我花了几个月的时间使用LINQ。现在已经是第二天性了,SQL顺序似乎很奇怪。

是哪!范围规则全部用SQL弄乱了。您可能要向同事指出的是LINQ是经过精心设计的,因此(1)变量和范围的引入从左到右(*),并且(2)查询在页面上出现的顺序是执行顺序。也就是说,当你说

from c in customers where c.City == "London" select c.Name

c出现在范围的左侧,并一直保留在右侧的范围内。事情发生的顺序是:首先评估“客户”。然后评估“ where”以过滤序列。然后通过“选择”投影过滤后的序列。

SQL没有此属性。如果你说

SELECT Name FROM Customers WHERE City = 'London'

然后将“名称”放在其右边而不是左边的范围内,并以完全混乱的顺序执行查询;首先评估中间子句,然后是最后一个子句,然后是第一个子句。对于我来说,这似乎很疯狂,而且很不直观,因为与LINQ一起工作了这么长时间。


(*)在LINQ中,使用join子句的作用域规则有些奇怪。但是除此之外,范围很好地嵌套了。


5
Nitpick:在德语中,“ Der Hund frisst das Fleisch”实际上与英语中的“狗吃肉”相同:)除此之外,一旦我发现它不是SQL,我发现LINQ Query语法非常可读。
Michael Stum

18
@gbjbaanb:我并不是在完全主观的基础上证明LINQ语法是合理的,因为它更易于理解。相反,我是在完全客观的基础上证明其合理性,因为设计LINQ语法时要考虑到工具的存在,并且设计更容易帮助用户构建LINQ查询,并且在思想上更容易理解。事件在查询中发生的顺序,因为它们在词法上顺序相同。
埃里克·利珀特

2
埃里克(Eric),从声明式编程的角度来看,我理解Linq比SQL更合适(我说是可读的)。但是,它比SQL更具可读性吗?没门。如果“狗吃红色的肉”很难,那么“从颜色为红色的厨房拿刀”要容易得多吗?实际上,我们所有人都在谈论和思考,就像“从厨房里把那把刀放在桌子上一样”。这就是SQL比LINQ更贴近现实生活的地方。
nawfal 2012年

6
我要从星巴克那里买一杯咖啡,这家商店营业至午夜。这听起来对您熟悉吗?它与SQL查询的顺序完全相同。是的,LINQ可能比执行标准SQL查询所必须生成的不必要代码更具可读性,但是LINQ并不比SQL查询本身更具可读性。是的 LINQ开发人员从左到右调整范围可能会比较有利,但是作为用户,我为什么要关心呢?我只关心可用性。
KyleM 2013年

8
@KyleM:当然;可用性是LINQ设计的一个非常重要的方面。特别是,适应生产力工具是关键。因为范围从左到右流到LINQ查询中,所以在您键入时c.,IDE已经知道类型c并可以提供IntelliSense。在LINQ中,您说“来自c的客户来自哪里c”。和繁荣时期,您可以通过列出的成员来获得IntelliSense的帮助Customer。在SQL当你输入“选择的名字从客户”你不能得到任何IDE帮助,告诉你,“名”是一个很好的选择,因为您键入name 之前 customers
埃里克·利珀特

22

像编程世界中的其他任何事物一样,您必须习惯语法,然后(可能)更易于阅读。

像编程世界中的任何其他事物一样,存在意大利面条代码或其他滥用行为的可能性。

像编程世界中的任何其他事物一样,您可以通过这种方式或另一种方式进行。

像编程世界中的任何其他事物一样,您的工作量可能会有所不同。


6

我看到一条注释/评论,它相对于LINQ / lambda声明了以下内容:“将代码写给人类可读,而不是计算机可读”。

我认为该语句有很多优点,但是,请考虑已经使用过全范围开发语言的开发人员(例如我本人),包括大会,程序,OO,托管,利用高吞吐量任务并行解决方案。

我为使我的代码具有尽可能高的可读性和可重用性,并采用许多GOF设计模式原则而感到自豪,以在众多不同的业务部门中提供生产质量体系和服务。

第一次遇到lambda表达式时,我想:“这到底是什么!?!” 它立即与我熟悉的(因此很舒服)显式声明式语法背道而驰。在职年龄小于5岁的年轻人却把它像天堂里的甘露一样轻拍了!

这是因为多年来,像计算机一样的思考(在语法意义上)都非常容易地转换为直接编码语法(与语言无关)。当您具有大约20岁以上(在我的情况下为30岁以上)的计算心态时,您必须意识到lambda表达式的最初语法冲击很容易转化为恐惧和不信任感。

也许OP中的同事来自与我本人相似的背景(即几次出现在街区附近),这在当时对他们来说是违反直觉的吗?我的问题是:您对此做了什么?您是否试图重新教育您的同龄人以了解内联语法的好处,还是因为没有“与程序在一起”而嘲笑/排斥他们?前者可能会看到您的同事走近您的思路,后者可能会使他们更加不信任LINQ / lambda语法,从而加剧了负面意见。

对于我自己,我必须重新教育自己的思维方式(如上文Eric所言,这并不是微不足道的思维转变,我不得不在80年代在Miranda编程,所以我分享了自己的函数式编程经验)但是一旦经历了这种痛苦,好处就显而易见了,但更重要的是,它的用法被过度使用(即为了使用而被使用),过于复杂和重复(在这种情况下考虑了DRY原理)。

作为不仅要编写大量代码而且还必须从技术上审查许多代码的人,当务之急是我理解这些原则,以便我可以公正地审查项目,并建议在哪里使用lambda表达式可能更有效/可读性强,并使开发人员考虑高度复杂的内联lambda表达式的可读性(在这种情况下,方法调用将使代码更具可读性,可维护性和可扩展性)。

因此,当有人说“不要拉姆达”时?或LINQ语法,而不是给他们打上卑鄙的口号,以帮助他们理解基本原理。他们毕竟可能有像我这样的“老派”背景。


有趣的评论-当我从强类型的静态语言切换到动态语言时,我有这个想法。花了我一段时间去适应(恐惧和不信任),现在我发现老实说很难回头。
Lunchmeat317 2014年

4

如果查询时间过长,Lambda表达式会导致代码可读性降低。但是,它比太多的嵌套循环要好得多。

最好将两者混合使用

如果更快(您需要更快)或更容易阅读,请用Lambda编写。


是的,但是是什么使linq / lamda 不易阅读?
BlackICE 2010年

抱歉,这比其他方法难读。
BlackICE 2010年

1

我认为,在大多数情况下(除非做一些非常奇怪的事情),取决于您是说“可读”是指某人了解正在发生的事情还是他们可以轻松地找到所有微小的细节。

我认为链接对前者有所帮助,但通常(尤其是在调试时)会伤害后者。

恕我直言,当我查看代码时,我不熟悉前者比后者更为重要,因此我发现它更具可读性。


好点子。后者通常包含良好的单元测试。
Scott Whitlock 2010年

因此,您认为linq评估的内部结构很难找到所有细节?您能否提供一个示例说明何时可能出现?
BlackICE 2010年

3
@David:Linq表达式不仅是惰性的,而且是表达式。linq表达式的接收代码实际上可以修改表达式,因此它可以做完全不同的事情,例如在s表达式上工作的lisp宏。例如,当使用linq to SQL时,它实际上采用表达式where e.FirstName == "John"并将其转换为SQL查询!它通过查看未编译(但已解析)的表达式来实现这一点,看到它是FirstName对持久实体上调用的属性的比较,并且它与字符串等进行了比较。发生了很多事情。
Scott Whitlock 2010年

@david通常,在您开始针对自己使用linq之前,我不会发现很难找到的细节。一个“简单”的示例花费了我很长时间来思考bugsquash.blogspot.com/2008/07/y-combinator-and-linq.html,但是在某些示例中还有很多必要的示例人们在dynamic,DynamicObject和IDynamicMetaObjectProvider区域中使用表达式所做的事情。您在哪里确定linq是有争议的,但是正如scott指出的,因为linq使用相同的表达式树,所以所有这些东西都可以在linq中使用。
比尔2010年

@Bill好吧,我给你带来的困难,但是y-combinator和高阶函数将伤害我们大多数人的大脑,就像递归一样,你懂了还是不懂。递归的实现是否更容易阅读(如果您都不知道)?
BlackICE 2010年

1

我发现LINQ语法直观易读,特别是因为他们将FROM放在它所属的开头,而不是像SQL那样放在中间。但是,IMO lambda令人困惑,并使代码更难阅读。


您为什么认为Lambda令人困惑?是类型推断吗?
ChaosPandion 2010年

1
@ChaosPandion:首先是类型推断,其次是符号。将=和a>放在一起看起来像> =,有人将其拧紧并向后书写,这往往使我的大脑陷入混乱。
梅森惠勒2010年

@Mason-您喜欢Haskell(\x -> x * x)或F#(fun x -> x * x)的语法吗?
ChaosPandion 2010年

@ChaosPandion:不,不是特别。Lambda可能很快就会写出来,但是我发现它们很难解析,尤其是当它们变得不那么复杂时。您的示例并没有那么糟,但是它们会变得更糟。
梅森惠勒2010年

6
@梅森:听起来您是在反对滥用lamda,而不是首先反对lamda。
Anon。

1

我同意您的观点,Linq语法与T-SQL并无显着差异。我认为您的同事可能真的反对将关系代码混入他的漂亮而有光泽的OO代码中。另一方面,函数式编程要习惯一些,并且要习惯它。


0

这取决于。显然,T-SQL独特地提供了一些DB关系解决方案。显然,LINQ独特地提供了一些OO解决方案。

然而; “比T-SQL更令人困惑吗?” -在第一个问题中进行了讨论/提出。显然,这暗示着现有答案都无法解决的某些功能,而是指责(显然是SQL熟悉的)批评家被困在过去。

因此,尽管我对LINQ的某些品质表示赞赏,并且并不太反对此处的现有答案,但我感到对这一点值得代表:

在熟悉LINQ多年之后,执行某些类型的组操作,外部联接和非等联接,使用复合键以及LINQ中的其他操作仍然让我感到有些畏缩。(特别是在针对具有性能敏感问题的关系后端时。)

from c in categories
join p in products on c equals p.Category into ps
from p in ps.DefaultIfEmpty()

如果您认为这很直观,那么您将获得更大的权力。;)


-4

Linq很烂是有原因的,只是尝试制作真实的例子,而不是这些教科书的例子。

尝试在linqlamdathings中显示此功能,您将看到,所有的美都消失了,而经典的方式仍然可读。更不用说延迟执行问题和设置断点了。

事实是,LinqLambdaThings在某些情况下非常有用,并且不认为它们可以取代你们从未理解过的所有漂亮的面向对象

    public IList<Customer> GetVipCustomers(IList<Customer> source, int maxCount)
    {
        var results = new SortedList<string,Customer>();

        foreach (Customer c in source)
        {
            if (maxCount == results.Count)
                break;

            if (c.IsVip && c.FirstName== "Aaron" || c.SecondName== "Aaron")
                results.Add(c.LastName, c);
        }

        return results.Values;
    }

6
我认为是这样,source.Where(c => c.IsVip && c.FirstName == "Aaron" || c.SecondName == "Aaron").Take(maxCount).OrderBy(d => d.LastName);但很难读懂您的意思,所以我可能犯了一个错误;)
jk。

1
您以何种方式认为这些是教科书示例?那实际上是生产使用代码。如果您同时提供了经典的“可读”代码和linq / lamda版本进行比较,而不是期望每个人都将其转换,将会有所帮助。
BlackICE 2013年

3
您是否专门注册以抱怨LINQ?
KChaloux

1
@KChaloux我认为他们只是曳说实话
JK。
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.