我怎么知道我的方法应该是可重用的?[关闭]


133

我在家照顾自己的生意,妻子来找我说

亲爱的..您可以在控制台中打印2018年全球所有的日光节约时间吗?我需要检查一下。

我非常高兴,因为那是我一直在等待一生的Java经验,并提出了:

import java.time.*;
import java.util.Set;

class App {
    void dayLightSavings() {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(
            zoneId -> {
                LocalDateTime dateTime = LocalDateTime.of(
                    LocalDate.of(2018, 1, 1), 
                    LocalTime.of(0, 0, 0)
                );
                ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
                while (2018 == now.getYear()) {
                    int hour = now.getHour();
                    now = now.plusHours(1);
                    if (now.getHour() == hour) {
                        System.out.println(now);
                    }
                }
            }
        );
    }
}

但是后来她说她只是在测试我是否是受过伦理培训的软件工程师,并告诉我看来我不是此后的人(从此处获取)。

应该注意的是,没有任何受过伦理培训的软件工程师会同意编写DestroyBaghdad程序。相反,基本的职业道德要求他编写一个DestroyCity程序,巴格达可以以此为参数。

而且我很喜欢,精致,好,你让我..传递任何一年你喜欢,在这里你去:

import java.time.*;
import java.util.Set;

class App {
    void dayLightSavings(int year) {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(
            zoneId -> {
                LocalDateTime dateTime = LocalDateTime.of(
                    LocalDate.of(year, 1, 1), 
                    LocalTime.of(0, 0, 0)
                );
                ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
                while (year == now.getYear()) {
                    // rest is same..

但是我如何知道要参数化多少(和什么)?毕竟,她可能会说..

  • 她想传递一个自定义的字符串格式化程序,也许她不喜欢我已经在打印的格式: 2018-10-28T02:00+01:00[Arctic/Longyearbyen]

void dayLightSavings(int year, DateTimeFormatter dtf)

  • 她只对某些月份感兴趣

void dayLightSavings(int year, DateTimeFormatter dtf, int monthStart, int monthEnd)

  • 她对某些时段感兴趣

void dayLightSavings(int year, DateTimeFormatter dtf, int monthStart, int monthEnd, int hourStart, int hourend)

如果您正在寻找一个具体的问题:

如果destroyCity(City city)好于destroyBaghdad()takeActionOnCity(Action action, City city)甚至更好?为什么/为什么不呢?

毕竟,我可以先用Action.DESTROYthen 来调用它Action.REBUILD,不是吗?

但是对我采取行动对我来说还不够,那又如何takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)呢?毕竟,我不想打电话给:

takeActionOnCity(Action.DESTORY, City.BAGHDAD);

然后

takeActionOnCity(Action.DESTORY, City.ERBIL);

等我可以做的时候:

takeActionOnGeographicArea(Action.DESTORY, Country.IRAQ);

ps:我只是围绕我提到的话提出了我的问题,我对任何国家,宗教,种族或世界上任何国家都没有反对。我只是想指出一点。



71
您在这里提出的观点是我多次尝试表达的观点:普遍性是昂贵的,因此必须以具体的,明显的利益为理由。但这远不止于此。编程语言是由设计人员创建的,旨在使某些通用性比其他通用性更容易,这会影响我们作为开发人员的选择。通过值对方法进行参数化很容易,而当这是您工具箱中最简单的工具时,诱惑就是不管是否对用户有意义,都使用它。
埃里克·利珀特

30
重用并不是您自己想要的。我们优先考虑重用,因为我们认为代码工件的构建成本很高,因此应该在尽可能多的场景中使用,以在这些场景中摊销该成本。观察通常不能证明这种信念是正确的,因此设计可重用性的建议经常被误用。设计代码以降低应用程序的总成本
埃里克·利珀特

7
你的妻子是不道德的人,因为他对你撒谎浪费了你的时间。她要求一个答案,并给出了建议的媒介。根据该合同,如何获得该输出仅在您和您自己之间。还有,destroyCity(target)destroyBagdad()!更不道德!什么样的怪物写了一个程序来消灭一个城市,更不用说世界上任何一个城市了?如果系统遭到破坏怎么办?另外,时间/资源管理(投入的精力)与道德有什么关系?只要口头/书面合同已按协议完成。
Tezra

25
我认为您可能对这个笑话读得太多了。这是关于计算机程序员如何做出不良道德决定的笑话,因为他们优先考虑技术因素而不是工作对人类的影响。它不打算成为有关程序设计的好建议。
埃里克·利珀特

Answers:


114

一直都是乌龟。

在这种情况下还是抽象。

好的实践编码是可以无限应用的,在某些情况下,您为了抽象而进行抽象,这意味着您已经走得太远了。很难找到一条线,因为这很大程度上取决于您的环境。

例如,我们有一些客户,他们首先要求简单的应用程序,然后又要求扩展。我们也有客户询问他们想要什么,并且通常从不回头寻求扩展。
您的方法因客户而异。对于第一个客户,先占先提取代码将是有好处的,因为您有理由确定以后需要重新访问该代码。对于第二个客户,如果期望他们在任何时候都不希望扩展应用程序,则可能不希望花费额外的精力(请注意:这并不意味着您没有遵循任何良好做法,而只是您可以避免做超出当前必要的事情。

我怎么知道要实现哪些功能?

我上面提到的原因是因为您已经陷入了这个陷阱:

但是我如何知道要参数化多少(和什么)?毕竟,她可能会说

“她可能会说”不是当前的业务要求。这是对未来业务需求的猜测。通常,不要以猜测为根据,而只是开发当前需要的内容。

但是,此处适用上下文。我不认识你妻子 也许您准确地估计出她实际上会想要这个。但是您仍然应该与客户确认这确实是他们想要的,因为否则您将花费​​时间开发永远不会最终使用的功能。

我怎么知道要实现哪种架构?

这比较棘手。客户不关心内部代码,因此您不能问他们是否需要内部代码。他们对此事的看法大多无关紧要。

但是,您仍然可以通过客户提出正确的问题来确认这样做的必要性。与其问有关体系结构,不如问他们对将来开发或扩展代码库的期望。您还可以询问当前目标是否有最后期限,因为您可能无法在必要的时间范围内实现您的理想架构。

我怎么知道何时进一步抽象我的代码?

我不知道在哪里读(如果有人知道,请告诉我,我会给你功劳的),但是一个很好的经验法则是,开发人员应该像一个穴居人一样数一数二

在此处输入图片说明 XKCD#764

换句话说,当被用于一个特定的算法/图案第三时间,应该被抽象,使得它是可重复使用(=可用许多倍)。

只是要清楚一点,我并不是说当使用的算法只有两个实例时,您不应该编写可重用的代码。当然,你可以说抽象的为好,但规则应该是你的三个实例必须抽象。

同样,这会影响您的期望。如果您已经知道需要三个或更多实例,那么您当然可以立即进行抽象。但是,如果仅猜测可能要执行更多次,则实现抽象的正确性完全取决于猜测的正确性。
如果猜对了,就节省了一些时间。如果您猜错了,那么您会浪费一些时间和精力,并且可能会破坏体系结构以实现最终不需要的东西。

如果destroyCity(City city)好于destroyBaghdad()takeActionOnCity(Action action, City city)甚至更好?为什么/为什么不呢?

这很大程度上取决于多种因素:

  • 任何城市都可以采取多种行动?
  • 这些动作可以互换使用吗?因为如果“ destroy”和“ rebuild”操作的执行方式完全不同,那么将两者合并为一个takeActionOnCity方法就没有意义了。

还应注意,如果递归地对此进行抽象,则最终将得到一个如此抽象的方法,它仅是运行另一个方法的容器,这意味着您已使该方法变得无关紧要且毫无意义。
如果您整个takeActionOnCity(Action action, City city)方法的主体最终不过是action.TakeOn(city);,那么您应该怀疑该takeActionOnCity方法是否真正有目的,或者仅仅是一个没有任何价值的额外层。

但是对我采取行动对我来说还不够,那又如何takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)呢?

同样的问题在这里弹出:

  • 您有地理区域用例吗?
  • 在城市和区域上执行的动作是否相同?
  • 可以在任何地区/城市采取任何措施吗?

如果您对这三个问题都可以肯定地回答“是”,那么就可以抽象一个。


16
我不能足够强调“一个,两个,许多”规则。抽象/参数化的可能性是无限的,但是有用的子集很小,通常为零。准确地知道哪个变体有价值,通常只能通过回顾来确定。因此,请遵守即时需求*,并根据新需求或事后观察的需要增加复杂性。*有时您很了解问题空间,因此可以添加一些东西,因为您知道明天需要它。但是,明智地使用此权力,也可能导致破产。
Christian Sauer

2
>“我不知道我在哪里读[..]”。您可能已经读过《编码恐怖:三个规则》
符文

10
确实存在“一,二,许多规则”,以防止您盲目地应用DRY来构建错误的抽象。关键是,两段代码可以看起来几乎完全相同,因此很容易将差异抽象出来。但是在早期,您不知道代码的哪些部分是稳定的,哪些不是稳定的;此外,事实证明他们实际上需要独立发展(不同的变更模式,不同的职责集)。在任何一种情况下,错误的抽象都会对您不利并造成阻碍。
FilipMilovanović18年

4
等待两个以上的“相同逻辑”示例使您可以更好地判断应抽象的内容以及如何抽象(实际上,这是关于管理具有不同变更模式的代码之间的依赖关系/耦合)。
FilipMilovanović18年

1
@kukis:现实的线应该画在2(根据Baldrickk的评论):零一对多(数据库关系就是这种情况)。但是,这为不必要的模式搜索行为打开了大门。两件事看起来可能有些相似,但这并不意味着它们实际上是相同的。但是,当第三个实例与第一个实例和第二个实例相似时,您可以做出更准确的判断,即它们的相似性确实是可重用的模式。因此,常识线画在3处,当在两个实例之间发现“图案”时,这会导致人为错误。
平坦

44

实践

这是软件工程SE,但是制作软件比工程更重要。没有通用算法可以用来确定多少可重用性就可以遵循或测量。像其他任何东西一样,设计程序的实践越多,您就会越能胜任。您将对“足够”的内容有更好的感觉,因为当您过多或太少地参数化时,您都会看到问题出在哪里,以及问题出在哪里。

现在,这并不是很有帮助,那么一些准则呢?

回头看看你的问题。有很多“她可能会说”和“我会说”。关于未来需求的理论上有很多说法。人类在预测未来时很沮丧。而你(很可能)是人类。软件设计的压倒性问题试图解释您所不知道的未来。

准则1:您将不需要它

说真的 停下来。通常,这种想象中的未来问题不会出现-而且肯定不会像您想象的那样出现。

准则2:成本/收益

太酷了,这个小程序花了您几个小时来编写?那么,如果您的妻子确实回来并要求这些东西怎么办?最坏的情况是,您花了几个小时来整理另一个程序。对于这种情况,没有太多时间使该程序更灵活。而且不会增加运行时速度或内存使用量。但是非平凡的程序有不同的答案。不同的方案有不同的答案。在某些时候,即使未来的演讲技巧不完善,成本显然也不值得。

准则3:关注常量

回头看看这个问题。在您的原始代码中,有很多恒定的整数。20181。恒定整数,恒定字符串...它们是最可能需要非恒定的东西。更好的是,它们只需要一点时间就可以进行参数化(或至少定义为实际常数)。但是要警惕的另一件事是行为不断。的System.out.println例如。这种关于使用的假设往往会在将来发生变化,并且修复起来的成本往往很高。不仅如此,但这样的IO会使功能不纯(以及时区有所获取)。参数化该行为可以使函数更纯净,从而提高灵活性和可测试性。以最小的成本获得巨大的收益(尤其是如果您System.out默认使用重载)。


1
这只是一个准则,1很好,但是您看一下它们,然后说“这会改变吗?” 并且可以使用更高阶的函数对println进行参数化-尽管Java在这些方面并不出色。
Telastyn

5
@KorayTugay:如果该程序确实适合您的妻子回家,那么YAGNI会告诉您您的初始版本是完美的,并且您不应该再花更多的时间来引入常量或参数。YAGNI需要 上下文 -您的程序是一次性解决方案,还是迁移程序仅运行了几个月,或者它是打算在几十年内使用和维护的庞大ERP系统的一部分?
布朗

8
@KorayTugay:将I / O与计算分开是一种基本的程序结构化技术。从数据过滤中分离出数据,从数据转换中分离出数据,从数据的呈现中分离出数据。您应该学习一些功能程序,然后才能更清楚地看到这一点。在函数式编程中,生成无限数量的数据,仅过滤出您感兴趣的数据,将数据转换为所需的格式,从中构造一个字符串并在5个不同的函数中打印该字符串是很常见的,每个步骤一个。
约尔格W¯¯米塔格

3
附带说明,强烈遵循YAGNI导致需要不断重构:“如果不进行连续重构使用,可能会导致代码混乱和大量返工,称为技术债务。” 因此,虽然YAGNI通常是一件好事,但它承担着重新访问和重新评估代码的巨大责任,这并不是每个开发人员/公司都愿意做的事情。
扁平的

4
@Telastyn:我建议将问题扩展为“这将永远不会改变,而代码的意图是否会在不命名常量的情况下被轻松读取?”即使对于永不改变的值,也可能仅出于保持事物可读性而命名它们。
扁平的

27

首先:任何出于安全意识的软件开发人员都不会出于任何原因不通过授权令牌就编写DestroyCity方法。

我也可以将任何具有明显智慧的命令写成当务之急,而不必将其应用于其他情况。为什么需要授权字符串连接?

其次:所有代码在执行时都必须完全指定

决定是硬编码到位还是推迟到另一层都无关紧要。在某种程度上,有一段用某种语言编写的代码既知道要销毁什么,又如何指示它。

那可能在同一目标文件中destroyCity(xyz),也可能在配置文件中:destroy {"city": "XYZ"}",或者可能是UI中的一系列单击和按键。

第三:

亲爱的..您可以在控制台中打印2018年全球所有的日光节约时间吗?我需要检查一下。

与以下要求完全不同:

她想通过一个自定义的字符串格式化程序,...仅对某些月份感兴趣,[和]对某些小时感兴趣...

现在,第二组要求显然需要一种更灵活的工具。它具有更广泛的目标受众和更广泛的应用领域。这里的危险是,世界上最灵活的应用程序实际上是机器代码的编译器。从字面上看,它是一个如此通用的程序,它可以构建任何东西以使计算机随心所欲(在其硬件限制内)。

一般而言,需要软件的人不需要通用的东西。他们想要一些特定的东西。通过提供更多选择,您实际上使他们的生活更加复杂。如果他们想要这种复杂性,他们会使用编译器,而不问您。

您的妻子要求提供功能,但未充​​分说明她对您的要求。在这种情况下,这似乎是故意的,总的来说,这是因为他们没有更好的了解。否则,他们将只使用编译器本身。因此,第一个问题是您没有要求提供有关她想做什么的更多详细信息。她是否想将其运行几年?她想要在CSV文件中吗?您没有发现她想做出自己的决定,也没有发现她要您为她做出决定的事情。一旦确定了需要推迟哪些决策,就可以确定如何通过参数(和其他可配置方式)传达这些决策。

话虽这么说,大多数客户会错过沟通,推定他们的想法,或者对某些细节(又称决策)一无所知,而这些细节他们真的很想自己做,或者他们真的不想做(但是听起来很棒)。这就是为什么像PDSA(计划-开发-研究-行为)之类的工作方法很重要的原因。您已经根据需求计划了工作,然后制定了一组决策(代码)。现在是时候由您本人或与您的客户一起学习它并学习新事物了,这些知识将有助于您今后的思考。最后,根据您的新见解采取行动-更新需求,完善流程,获取新工具等。然后重新开始计划。随着时间的推移,这将揭示任何隐藏的要求,并向许多客户证明进度。

最后。你的时间很重要 ; 这是非常真实而且非常有限的。您做出的每个决策都包含许多其他隐藏的决策,这就是开发软件所要解决的。将决策作为参数延迟可能会使当前函数更简单,但确实会使其他地方更复杂。该决定在其他位置是否有用?在这里更相关吗?到底是谁的决定?您正在决定;这是编码。如果您经常重复执行决策集,那么将它们编入某种抽象中将有非常实际的好处。XKCD在这里很有用。这与系统级别有关,例如功能,模块,程序等。

一开始的建议意味着您的函数无权做出的决定应作为参数传递。问题在于一个DestroyBaghdad函数实际上可能是具有该权限的函数。


+1喜欢有关编译器的部分!

4

这里有很多漫长的答案,但老实说,我认为这非常简单

函数中不属于函数名称的任何硬编码信息都应作为参数。

所以在你的职能

class App {
    void dayLightSavings() {
        final Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();
        availableZoneIds.forEach(zoneId -> {
            LocalDateTime dateTime = LocalDateTime.of(LocalDate.of(2018, 1, 1), LocalTime.of(0, 0, 0));
            ZonedDateTime now = ZonedDateTime.of(dateTime, ZoneId.of(zoneId));
            while (2018 == now.getYear()) {
                int hour = now.getHour();
                now = now.plusHours(1);
                if (now.getHour() == hour) {
                    System.out.println(now);
                }
            }
        });
    }
}

你有:

The zoneIds
2018, 1, 1
System.out

所以我将所有这些以一种或另一种形式移至参数。您可能会争辩说zoneIds在函数名称中是隐式的,也许您想通过将其更改为“ DaylightSavingsAroundTheWorld”或其他方式来使它更进一步。

您没有格式字符串,因此添加一个是功能请求,您应该将您的妻子推荐给家庭Jira实例。可以将其放在待办事项中,并在适当的项目管理委员会会议上确定优先级。


1
您(向OP致辞)肯定不应该添加格式字符串,因为您不应打印任何内容。关于此代码的一件事是绝对禁止其重复使用,它可以打印。当它们离开DST时,应该返回区域或区域的映射。(尽管为什么它只能识别时去 DST,而不是 DST,我不明白,这似乎不匹配的问题发言。)
大卫·康拉德

要求是打印到控制台。您可以通过在输出流作为参数传递mitgate紧couplung我建议
伊万

1
即使这样,如果您希望代码可重用,则不应打印到控制台。编写一个返回结果的方法,然后编写一个获取并打印结果的调用方。这也使其可测试。如果您确实想让它产生输出,则不会传递输出流,而会传递消费者。
戴维·康拉德

我们的产品流是消费者
Ewan


4

简而言之,不要为可重用性而设计您的软件,因为最终用户不会在乎您的功能是否可以重用。取而代之的是,设计可理解性的工程师-我的代码是否容易被他人使用或让我以后的健忘的自我理解?-和设计灵活性-当我不可避免地不得不修正错误,添加功能或以其他方式修改功能时,我的代码将承受多少更改?客户唯一关心的是,当她报告错误或要求更改时,您可以多快做出响应。偶然问到有关设计的这些问题往往会导致代码可重用,但是这种方法使您专注于避免在代码寿命期间将要面对的实际问题,因此您可以更好地为最终用户提供服务,而不是追求崇高的,不切实际的“工程”的理想取悦于胡须。

对于您提供的示例这样简单的事情,您的初始实现就好,因为它很小,但是如果您尝试将太多的功能灵活性(而不是设计灵活性)塞入其中,那么这种简单的设计将变得难以理解和脆弱。一个过程。以下是我对设计复杂系统以提高可理解性和灵活性的首选方法的解释,我希望它将证明我的意思。我不会将这种策略用于可能在一个过程中用少于20行编写的东西,因为这么小的东西已经可以满足我对可理解性和灵活性的要求。


对象,不是过程

不要将诸如老式模块之类的例程与一堆例程结合使用来执行软件应做的事情,而应考虑将域建模为对象,这些对象相互作用并合作完成手头的任务。最初,面向对象范式中的方法被创建为对象之间的信号,以便Object1可以告诉Object2自己执行其操作,无论是什么,并且可能接收返回信号。这是因为面向对象的范式本质上是关于对域对象及其交互进行建模的,而不是一种组织命令式范式的相同旧功能和过程的理想方法。在这种情况下void destroyBaghdad例如,与其尝试编写一种无上下文的通用方法来处理对巴格达或任何其他事物的破坏(这可能会很快变得复杂,难以理解和脆弱),每个可以被破坏的事物都应负责了解如何自毁。例如,您有一个描述可破坏事物行为的界面:

interface Destroyable {
    void destroy();
}

然后,您将拥有一个实现此接口的城市:

class City implements Destroyable {
    @Override
    public void destroy() {
        ...code that destroys the city
    }
}

City不需要销毁实例的任何事情都不会关心它是如何发生的,因此没有理由将该代码存在于外部之外的任何地方City::destroy,并且实际上,City对自身外部内部工作方式的深入了解会紧密耦合,从而减少可修改性,因为如果您需要修改行为的话,必须考虑这些外部因素City。这是封装背后的真正目的。可以将其视为每个对象都有其自己的API,该API应该使您能够使用它执行所需的任何操作,因此可以让它担心满足您的请求。

委派,而非“控制”

现在,您的实施级别是City还是Baghdad取决于破坏城市的过程究竟有多普遍。极有City可能由较小的碎片组成,而这些碎片需要分别销毁以完成对城市的彻底销毁,因此在这种情况下,这些碎片中的每一个也将实现Destroyable,并且将由指示City销毁它们。自己以同样的方式从外部有人要求City摧毁自己。

interface Part extends Destroyable {
    ...part-specific methods
}

class Building implements Part {
    ...part-specific methods
    @Override
    public void destroy() {
       ...code to destroy a building
    }
}

class Street implements Part {
    ...part-specific methods
    @Override
    public void destroy() {
        ...code to destroy a building
    }
}

class City implements Destroyable {
    public List<Part> parts() {...}

    @Override
    public void destroy() {
        parts().forEach(Destroyable::destroy);            
    }
}

如果您想变得疯狂并实现将a Bomb放置在某个位置并破坏一定半径内的所有内容的想法,则可能看起来像这样:

class Bomb {
    private final Integer radius;

    public Bomb(final Integer radius) {
        this.radius = radius;
    }

    public void drop(final Grid grid, final Coordinate target) {
        new ObjectsByRadius(
            grid,
            target,
            this.radius
        ).forEach(Destroyable::destroy);
    }
}

ObjectsByRadius表示Bomb从输入中针对进行计算的一组对象,因为Bomb并不关心如何进行该计算,只要它可以与这些对象一起使用即可。顺便说一句,这是可重用的,但是主要目的是将计算与丢弃Bomb对象和销毁对象的过程隔离开来,以便您可以理解每个零件以及它们如何装配在一起并更改单个零件的行为,而不必重塑整个算法。 。

互动而非算法

与其尝试猜测复杂算法的正确参数数量,不如将流程建模为一组相互作用的对象,每个对象的作用都非常狭窄,这更有意义,因为它将使您能够对复杂度进行建模。通过这些定义明确,易于理解且几乎不变的对象之间的交互进行处理。如果正确完成,这甚至可以使某些最复杂的修改变得微不足道,例如实现一个或两个接口并重新处理在main()方法中实例化的对象。

我会为您的原始示例提供一些帮助,但老实说,我无法弄清楚“打印...日光节约”的含义。关于这一类问题,我可以说的是,无论何时执行计算,都可以用多种方式格式化其结果,我更喜欢将其分解的方式是这样的:

interface Result {
    String print();
}

class Caclulation {
    private final Parameter paramater1;

    private final Parameter parameter2;

    public Calculation(final Parameter parameter1, final Parameter parameter2) {
        this.parameter1 = parameter1;
        this.parameter2 = parameter2;
    }

    public Result calculate() {
        ...calculate the result
    }
}

class FormattedResult {
    private final Result result;

    public FormattedResult(final Result result) {
        this.result = result;
    }

    @Override
    public String print() {
        ...interact with this.result to format it and return the formatted String
    }
}

由于您的示例使用的Java库中的类不支持此设计,因此您可以直接使用的API ZonedDateTime。这里的想法是,每个计算都封装在其自己的对象中。它不假定应运行多少次或应如何格式化结果。它只与执行最简单的计算形式有关。这使得它既易于理解又可以灵活地进行更改。同样,Result则仅涉及封装计算结果,而FormattedResult则仅涉及与交互以Result根据我们定义的规则对其进行格式化。通过这种方式,我们可以为每个方法找到理想数量的参数,因为它们每个都有一个定义明确的任务。只要接口不更改,修改前进的过程也要简单得多(如果您已适当地最小化了对象的职责,那么它们就不太可能这样做)。我们的main()方法可能如下所示:

class App {
    public static void main(String[] args) {
        final List<Set<Paramater>> parameters = ...instantiated from args
        parameters.forEach(set -> {
            System.out.println(
                new FormattedResult(
                    new Calculation(
                        set.get(0),
                        set.get(1)
                    ).calculate()
                ).print()
            );
        });
    }
}

事实上,面向对象编程是专门为解决命令式范式的复杂性/灵活性问题而开发的,因为对于如何最佳化还没有一个好的答案(每个人都可以同意或独立达成)。在成语中指定命令式功能和过程。


这是一个非常详细且经过深思熟虑的答案,但不幸的是,我认为它没有达到OP真正要求的程度。他并不是在寻求有关良好的OOP做法的课程来解决这个似是而非的例子,而是在询问我们决定在解决方案与泛化上投入时间的标准。
maple_shaft

@maple_shaft也许我错过了分数,但我想你也有。OP不会询问时间与一般化的投资。他问道:“我怎么知道我的方法应该是可重用的?” 他继续在自己的问题中问:“如果destroyCity(城市)比destroyBaghdad()更好,takeActionOnCity(行动动作,City城市)会更好吗?为什么/为什么不呢?” 我提出了一种工程解决方案的替代方法,我认为该方法解决了弄清楚如何通用化方法的问题,并提供了支持我的主张的示例。对不起,您不喜欢它。
Stuporman

@maple_shaft坦白说,只有OP才能确定我的回答是否与他的问题有关,因为我们其他人可能会为保卫我们对他意图的解释而进行的战争,所有这些都可能同样是错误的。
Stuporman

@maple_shaft我添加了一个简介,试图阐明它与问题的关系,并在答案和示例实现之间提供了清晰的界限。那个更好吗?
Stuporman

1
老实说,如果您应用所有这些原则,答案将自然而然,流畅且可读性强。此外,多变而不必大惊小怪。我不知道你是谁,但我希望你有更多!我一直在为合理的OO淘汰Reactive代码,它的大小总是一半,更易于阅读,更可控制,并且仍然具有线程/拆分/映射功能。我认为React适用于不了解您刚刚列出的“基本”概念的人们。
Stephen J

3

经验领域知识代码审查。

而且,无论您有多少经验领域知识团队,都无法避免按需进行重构。


使用Experience,您将开始识别所编写的非领域特定方法(和类)中的模式。而且,如果您对DRY代码完全感兴趣,那么在编写本能会在将来编写变体的方法时,您会感到不快。因此,您将直观地编写参数化的最小公分母。

(这种经验也可能本能地转移到您的某些域对象和方法中。)

使用领域知识,您将了解哪些业务概念紧密相关,哪些概念具有变量,哪些是静态的,等等。

通过代码审查,参数化过高和过低的参数化更有可能在其成为生产代码之前就被捕获,因为您的同行(希望)在总体上都具有独特的经验和观点


也就是说,新开发人员通常不会立即拥有这些Spidey Senses或经验丰富的同行。而且,即使是经验丰富的开发人员也可以从基本准则中受益,以指导他们提出新要求-或度过迷雾笼罩的日子。因此,这是我建议的一个开始

  • 从简单的实现开始,以最小的参数化。
    (显然,包括您已经知道需要的所有参数...)
  • 删除魔术数字和字符串,将其移至配置和/或参数
  • 将“大型”方法分解为较小的,名称明确的方法
  • 将高度冗余的方法(如果方便)重构为一个公分母,以参数化差异。

这些步骤不一定按规定的顺序进行。如果您坐下来编写一个已经知道与现有方法高度冗余的方法,请在方便时直接进行重构(如果重构所需的时间不会比编写,测试和维护两种方法所需的时间多得多)。

但是,除了具有丰富的经验等等,我建议您使用非常简单的代码DRY-ing。重构明显的违规并不难。而且,如果您太热心了,您可能会得到比“ WET”更难读,理解和维护的“ over-DRY”代码。


2
因此,没有正确的答案If destoryCity(City city) is better than destoryBaghdad(), is takeActionOnCity(Action action, City city) even better?吗?这是一个是/否问题,但没有答案,对吗?还是最初的假设是错误的,destroyCity(City)可能不一定会更好,而实际上取决于?因此,这并不意味着我不是一名没有经过道德培训的软件工程师,因为我一开始就没有任何参数就直接实现了?我的意思是我要问的具体问题的答案是什么?
Koray Tugay

您的问题有点问一些问题。标题问题的答案是:“经验,领域知识,代码审查……并且……不要害怕重构”。对于任何具体的“方法ABC的参数是否正确”问题的答案是……“我不知道。为什么要问?它当前拥有的参数数量有问题吗??它。解决它。“ ...我可能会将您引荐给“ POAP”以获取进一步的指导:您需要了解为什么要做自己的事情!
svidgen

我的意思是...让我们甚至从该destroyBaghdad()方法上退一步。上下文是什么?这是一款视频游戏,游戏结束时导致巴格达被摧毁????如果是这样…… destroyBaghdad()可能是一个非常合理的方法名称/签名……
svidgen

1
因此,您不同意我的问题中引用的报价,对吗?It should be noted that no ethically-trained software engineer would ever consent to write a DestroyBaghdad procedure.如果您和纳撒尼尔·波伦斯坦在房间里,您会认为他确实有赖于他,但他的说法不正确吗?我的意思是,很多人都在花时间和精力来回答问题,这很美丽,但是我看不到任何具体答案。蜘蛛侠的感觉,代码审查。。但是答案是is takeActionOnCity(Action action, City city) better? null什么?
Koray Tugay

1
@svidgen当然,另一种无需费力的抽象方法就是反转依赖关系-让函数返回城市列表,而不是对城市进行任何操作(例如原始代码中的“ println”)。如有必要,可以进一步抽象,但是仅此一项更改就可以解决原始问题中大约一半的新增要求-而不是一个可以处理各种不良事情的不纯函数,您只需拥有一个函数它返回一个列表,而调用者则做了不好的事情。
罗安

2

与质量,可用性,技术债务等相同的答案:

用户就像一样可重用,1需要他们

从本质上讲,这是一个判断电话-设计和维护抽象的成本是否将由成本(=时间和精力)所偿还,这将为您节省大量时间。

  • 注意“下线”这句话:这里有一个回报机制,所以这取决于您将进一步处理此代码的数量。例如:
    • 这是一个一次性的项目,还是会在长期内逐步改善?
    • 您对设计有信心吗,或者您可能会为下一个项目/里程碑而报废或以其他方式大幅度更改它(例如,尝试其他框架)?
  • 预计收益还取决于您预测未来(应用程序的更改)的能力。有时,您可以合理地看到您的应用将要使用的场所。更多的时间,您认为可以,但实际上却不能。经验法则是YAGNI原则三项原则 -都强调根据现在的知识进行工作。

1 这是一个代码构造,因此在这种情况下,您就是“用户”-源代码的用户


1

您可以遵循一个清晰的过程:

  • 为一个本身就是“事物”的功能编写一个失败的测试(即,不是某个功能的任意分割,而其中的一半都没有意义)。
  • 编写绝对的最小代码,使其通过绿色,而不是多行。
  • 冲洗并重复。
  • (如有必要,请进行不懈的重构,由于测试覆盖范围广,因此应该很容易。)

至少在某些人看来,这是非常好的代码,因为它越小越好,每个完成的功能都需要尽可能少的时间(如果您看完了,可能不正确)重构后的产品),并且具有很好的测试覆盖率。还可以明显避免过度设计的过于通用的方法或类。

这也为您提供了明确的指示,说明何时使事物通用以及何时进行专门化。

我发现您的城市示例很奇怪;我很可能永远不会对城市名称进行硬编码。很明显,以后无论您在做什么,都将包括其他城市。但是另一个例子是颜色。在某些情况下,可能会硬编码“红色”或“绿色”。例如,交通信号灯是一种无处不在的颜色,您可以摆脱它(并且可以随时重构)。区别在于,“红色”和“绿色”在我们的世界中具有通用的“硬编码”含义,它永远不可能改变,而且也没有任何替代选择。

您的第一个夏令时方法被打破了。虽然符合规范,但硬编码的2018特别糟糕,因为a)技术“合同”中未提及(在这种情况下,使用方法名称),并且b)很快就会过时,因此会损坏从一开始就包含在内。对于与时间/日期相关的事物,由于时间在不断变化,因此很难对特定值进行硬编码。但是除此之外,其他所有问题都需要讨论。如果给它一个简单的年份,然后始终计算完整年份,请继续。您列出的大多数内容(格式设置,较小范围的选择等)都表示您的方法做得太多,它可能应该返回值的列表/数组,以便调用者自己进行格式设置/过滤。

但归根结底,这大部分是意见,品味,经验和个人偏见,因此不要为此烦恼太多。


关于倒数第二段-查看最初给出的“要求”,即第一种方法所基于的要求。它指定了2018年,因此该代码在技术上是正确的(并且可能与您的功能驱动方法匹配)。
dwizum

@dwizum,关于要求是正确的,但是方法名称有误导性。在2019年,任何仅查看方法名称的程序员都将假设它正在执行任何操作(可能返回当前年份的值),而不是2018年...我将在答案中加上一句话以更清楚地说明我的意思。
AnoE

1

我认为有两种可重用的代码:

  • 可重用的代码,因为它是基本的东西。
  • 可重用的代码,因为它具有随处可见的参数,覆盖和挂钩。

第一种可重用性通常是一个好主意。它适用于列表,哈希表,键/值存储,字符串匹配器(例如regex,glob等),元组,统一,搜索树(深度优先,广度优先,迭代加深等)。 ,解析器组合器,缓存/内存,数据格式的读取器/写入器(S表达式,XML,JSON,protobuf等),任务队列等。

这些事情以一种非常抽象的方式是如此普遍,以至于它们在日常编程中被重新使用。如果您发现自己编写的特殊用途代码(如果将其抽象化/ 泛化起来会更简单)(例如,如果我们有“客户订单列表”,则可以丢弃“客户订单”内容以获得“列表”) ),那么最好将其删除。即使不被重复使用,它也可以使我们分离不相关的功能。

第二类是我们有一些具体的代码,可以解决一个实际的问题,但是可以通过做出很多决策来解决。我们可以通过“软编码”这些决策来使其更通用/可重用,例如将它们转换为参数,使实现复杂化并烘烤更具体的细节(即了解我们可能需要覆盖哪些钩子)。您的示例似乎是这种情况。这种可重用性的问题在于,我们最终可能会试图猜测其他人或我们未来的用例。最终,我们可能会拥有太多参数,以致我们的代码无法使用,更不用说可重用了!换句话说,调用时比编写我们自己的版本需要更多的精力。这就是YAGNI(您将不需要它)很重要的地方。很多时候,这种对“可重用”代码的尝试最终都不会被重用,因为它可能与参数无法解释的那些用例更根本地不兼容,或者那些潜在的用户宁愿自己动手(哎,看看所有标准和库,其作者以单词“ Simple”开头,以区别于以前的版本!)。

第二种形式的“可重用性”基本上应该在需要的基础上完成。当然,您可以在其中添加一些“显而易见的”参数,但不要开始尝试预测未来。亚尼


我们可以说您同意我的初衷很好吗,即使是对年份也进行了硬编码的地方?或者,如果您最初是实现该要求的,那么您是否会在第一时间将年份作为参数?
Koray Tugay

您的第一个建议是很好,因为要求是一个一次性脚本来“检查”。它没有通过“道德”测试,但没有通过“没有教条”测试。“她可能会说……”正在发明您将不需要的要求。
华宝

没有更多信息,我们不能说哪个“毁灭城市”是“更好的”:destroyBaghdad是一次性脚本(或者至少是幂等的)。也许摧毁任何城市都会有所改善,但是如果destroyBaghdad向底格里斯河泛滥,该怎么办呢?对于摩苏尔和巴士拉而言,这可能是可重用的,但对于麦加或亚特兰大而言,则不可重用。
华宝

我明白了,因此您不同意报价单的所有者纳撒尼尔·波伦斯坦(Nathaniel Borenstein)。通过阅读所有这些回复和讨论,我试图慢慢理解我的想法。
Koray Tugay

1
我喜欢这种差异。这并不总是很清楚,并且总是有“边界案例”。但是总的来说,我还是“构建模块”(通常以static方法的形式)的拥护者,这些构建模块纯粹是功能性的并且是低级的,与此相反,决定“配置参数和挂钩”通常是您必须建立一些要求合理的结构
Marco13 '18 / 12/1

1

已经有许多出色而详尽的答案。它们中的一些深入到特定的细节中,总体上对软件开发方法论提出了某些观点,并且其中某些确实包含有争议的元素或“观点”。

Warbo答案已经指出了不同类型的可重用性。即,某事物是否因为它是基本构建块而可重用,或者某事物是否由于它是某种“通用”而可重用。提到后者,我认为有一些措施可重用性:

一种方法是否可以模仿另一种方法。

关于问题的示例:想象一下该方法

void dayLightSavings()

是客户要求的功能的实现。因此,这将是其他程序员应该使用的东西,因此是一种公共方法,例如

publicvoid dayLightSavings()

如您在答案中所示,这可以实现。现在,有人想用年份来参数化它。所以你可以添加一个方法

publicvoid dayLightSavings(int year)

并将原始实现更改为

public void dayLightSavings() {
    dayLightSavings(2018);
}

下一个“功能请求”和概括遵循相同的模式。因此,仅当有对最通用形式的需求时,您可以实现它,知道这种最通用形式允许更具体实现的简单实现:

public void dayLightSavings() {
    dayLightSavings(2018, 0, 12, 0, 12, new DateTimeFormatter(...));
}

如果您预期将来会有扩展和功能请求,并且有一些时间可支配,并且想要度过一个无聊的周末(可能无用),则可以从一开始就从最通用的一个开始。但作为私有方法。只要您只公开客户要求的简单方法作为公共方法,就可以保证安全。

tl; dr

实际上,问题不在于“一种方法应该有多可重用”。问题是公开了多少这种可重用性,以及API的外观。创建一个可以经受时间考验的可靠API(即使以后还会提出进一步的要求)是一项艺术,也是一种技巧,而这个主题太复杂了,无法在此处进行介绍。首先看看Joshua Bloch的演示文稿API设计手册Wiki


dayLightSavings()dayLightSavings(2018)对我来说,打电话似乎不是一个好主意。
Koray Tugay '18

@KorayTugay当最初的要求是打印“ 2018年的夏时制”时,就可以了。事实上,这正是您最初实施的方法。如果它应该显示“ 当前年度的夏令时,那么您当然会打电话给dayLightSavings(computeCurrentYear());... ...
Marco13 '18

0

一个好的经验法则是:您的方法应与…可重用一样可重用。

如果希望只在一个地方调用方法,则该方法应该仅具有调用站点已知的参数,而该参数不适用于此方法。

如果有更多的呼叫者,则可以引入新的参数,只要其他呼叫者可以传递这些参数即可;否则,您可以引入新的参数。否则,您需要新的方法。

随着调用者数量的及时增加,您需要为重构或重载做好准备。在许多情况下,这意味着您应该安全地选择表达式并运行IDE的“提取参数”操作。


0

简短的答案:通用模块与其他代码的耦合度或依赖性越小,它的可重用性就越高。

您的示例仅取决于

import java.time.*;
import java.util.Set;

因此从理论上讲,它可以高度重用。

实际上,我认为您不会再有需要此代码的用例,因此,按照yagni原则,如果不超过3个需要此代码的项目,我将无法重用。

可重用性的其他方面是易用性和文档编制,这些方面与“ 测试驱动开发”紧密相关:如果您有一个简单的单元测试来演示/记录易于使用的通用模块作为lib用户的编码示例,这将很有帮助。


0

这是陈述我最近创造的规则的好机会:

成为一名优秀的程序员意味着能够预测未来。

当然,这绝对是不可能的!毕竟,您永远无法确定以后将对哪些概括有用,您将要执行哪些相关任务,用户想要哪些新功能等等。但是,有时经验会带给您大概有用的粗略概念。

您需要权衡的其他因素是,将涉及多少额外的时间和精力,以及使代码变得更加复杂。有时您很幸运,解决更普遍的问题实际上更简单!(至少从概念上讲,如果不是在代码量上。)但是,更常见的是,这不仅需要花费大量的复杂性,而且是费时费力的。

因此,如果您认为很有可能需要一般化,那么通常值得这样做(除非它增加了很多工作或复杂性);但如果看起来可能性很小,那么就可能不是(除非这非常容易和/或简化代码)。

(对于最近的一个示例:上周给了我一个规范,该规范指定了系统在某件事过期后应在2天后采取的操作。因此,我当然将2天的时间作为参数。本周,商务人士很高兴,因为他们我很幸运:这是一个简单的更改,我猜想它很有可能会被使用。通常很难判断,但是仍然值得尝试预测,经验通常是一个很好的指导)


0

首先,“我怎么知道我的方法应该是可重用的?”的最佳答案是“经验。”这样做数千次,您通常会得到正确的答案。但是作为预告片,我可以给您最后一个该答案行:您的客户将告诉您应该寻求多少灵活性以及概括的层次。

这些答案中有许多都有特定的建议。我想给出一些更通用的东西...因为具有讽刺意味的乐趣实在难以忘怀!

正如一些答案所指出的那样,普遍性是昂贵的。但是,事实并非如此。不总是。了解费用对于玩可重用性游戏至关重要。

我专注于将事情从“不可逆”变为“可逆”。这是一个平滑的规模。唯一真正不可逆的事情是“花费在项目上的时间”。您将永远不会收回这些资源。Windows API等“金色手铐”情况的可逆性稍差。不推荐使用的功能在该API中保留了数十年,因为Microsoft的业务模型要求使用它。如果您的客户的关系将因撤消某些API功能而永久受损,则应将其视为不可逆转。从规模的另一端看,您拥有诸如原型代码之类的东西。如果您不喜欢它的去向,可以将其丢弃。内部使用API​​的可逆性稍差。它们可以重构而不会打扰客户,时间(所有资源中最不可逆的资源!)

因此,将其规模化。现在,您可以应用启发式方法:可逆性越强,就越可以将其用于将来的活动。如果某些事情不可逆转,请仅将其用于具体的客户驱动任务。这就是为什么您会看到极端编程中的那些原理的原因,这些原理仅建议执行客户要求的操作,仅此而已。这些原则可以确保您不会做后悔的事情。

诸如DRY原理之类的东西提出了一种移动平衡的方法。如果您发现自己重复一遍,那将是一个创建基本上是内部API的机会。没有客户看到它,因此您可以随时对其进行更改。拥有此内部API之后,现在就可以开始使用前瞻性的东西了。您认为您的妻子要给您多少个基于时区的任务?您还有其他客户想要基于时区的任务吗? 您在这里的灵活性是由当前客户的具体需求所决定的,它可以满足未来客户潜在的未来需求。

这种分层的思维方法(自然而然地来自DRY)自然地提供了您想要的概括,而不会浪费。但是有限制吗?当然有。但是要看到它,您必须看到森林覆盖树木。

如果您有许多灵活性层,那么它们通常会导致无法直接控制面对您的客户的层。我拥有的软件曾负责向客户解释为什么他们无法获得他们想要的东西,这是因为他们在10层的内部构建了灵活性,而这些层却从未出现过。我们把自己写在一个角落。我们以我们认为需要的所有灵活性结为纽带。

因此,在执行泛化/ DRY技巧时,请始终对您的客户保持关注。您认为您的妻子接下来会要求什么?您是否有能力满足这些需求?如果您有诀窍,客户将有效地告诉您他们未来的需求。如果您没有诀窍,那么,我们大多数人都只能依靠猜测!(尤其是与配偶!)某些客户将需要极大的灵活性,并愿意接受您使用所有这些层进行开发所产生的额外费用,因为他们直接受益于这些层的灵活性。其他客户有固定的固定要求,他们希望开发更直接。 您的客户将告诉您应该寻求多少灵活性以及概括的层次。


那么应该有其他人这样做了10000次,那么为什么我可以做10000次并从别人那里学到经验呢?因为每个人的答案都不同,所以经验丰富的答案不适用于我吗?另外Your customer will tell you how much flexibility and how many layers of generalization you should seek.这是什么世界?
Koray Tugay

@KorayTugay这是一个商业世界。如果您的客户没有告诉您该怎么做,那么您的听觉就不够好。当然,他们不会总是用言语告诉您,但是他们会以其他方式告诉您。经验可以帮助您聆听他们更微妙的信息。如果您还不具备此技能,请找到公司中确实有能力聆听那些细微的客户提示并加以指导的人。即使他们是首席执行官或市场营销人员,也会有人具备这种技能。
Cort Ammon

在您的特定情况下,如果您由于忙于编写此时区问题的通用版本而不是将特定的解决方案混为一谈而未能清除垃圾,那么您的客户会有何感受?
Cort Ammon '18

因此,您是否同意我的第一种方法是正确的方法,而不是对年份进行参数化,而是首先对2018年进行硬编码?(顺便说一句,我想那不是真正地在听我的顾客,垃圾例子。那是在了解你的顾客。变更为2018年。)感谢您的宝贵时间并回答问题。
Koray Tugay,

@KorayTugay不知道任何其他细节,我想说硬编码是正确的方法。您无法知道是否需要将来的DLS代码,也无法知道她接下来可能会发出什么样的请求。如果您的客户想对您进行测试,他们将获得= D
Cort Ammon '18

0

没有经过专业培训的软件工程师会同意编写DestroyBaghdad程序。相反,基本的职业道德要求他编写一个DestroyCity程序,巴格达可以以此为参数。

这在高级软件工程界被称为“笑话”。笑话不必一定是我们所说的“真实”,尽管有趣的是,笑话通常必须暗示某些真实情况。

在这种特定情况下,“笑话”不是“真”。我们可以放心地假设,编写一项摧毁任何城市的一般程序所涉及的工作超出了摧毁一个城市所需的数量级。特定城市市。否则,任何摧毁了一个或几个城市的人(可以说是圣经中的约书亚,或者杜鲁门总统)都可以轻易地概括自己所做的一切,并且能够随意摧毁任何城市。事实并非如此。这两个人著名的销毁少数特定城市的方法不一定在任何时间都适用于任何城市。另一个墙壁的共振频率不同或高空防空能力较好的城市,将需要对方法进行微小或根本性的改变(小号喇叭或火箭)。

这也导致代码维护工作以防止随时间的变化:由于现代的构建方法和无处不在的雷达,现在有很多城市都不会采用这两种方法。

开发和测试,它会摧毁一个完全通用的手段的任何城市,你同意毁灭之前一个城市,是一个拼命低效的做法。在没有经过证明的要求的情况下,没有受过道德训练的软件工程师会试图将问题推广到一定程度,其要求的工作量要比其雇主/客户实际需要支付的工作量大几个数量级。

那是真的吗?有时添加一般性是微不足道的。那么,当这样做很简单时,我们是否应该总是添加通用性呢?由于长期的维护问题,我仍然会说“不,并非总是如此”。假设在撰写本文时,所有城市都基本相同,所以我继续进行DestroyCity。一旦编写了此代码,再加上集成测试(由于输入的有限数量的空间),该迭代将遍历每个已知城市,并确保该函数在每个已知城市上都有效(不确定如何运行。可能会调用City.clone()和销毁克隆?

在实践中,该功能仅用于销毁巴格达,假设有人在建造一座新城,该城对我的技术具有抵抗力(它在地下深处或其他地方)。现在,我针对一个甚至根本不存在的用例进行了集成测试失败,并且在继续针对伊拉克无辜平民的恐怖运动之前,我必须弄清楚如何消灭地下世界。不管这是否合乎道德,这都是愚蠢的浪费了我的时间

那么,您是否真的想要一个可以输出任何一年的夏令时,仅输出2018年数据的功能?也许可以,但是将测试用例放在一起肯定会需要少量的额外工作。要获得比您实际拥有的时区数据库更好的时区数据库,可能需要花费大量的精力。例如,1908年,安大略省亚瑟港镇的夏令时开始于7月1日。那是在您操作系统的时区数据库中吗?不要以为,所以您的广义函数是错误的。关于编写无法兑现承诺的代码,没有什么特别的道德要求。

好吧,因此,在适当的警告下,很容易编写一个函数来执行时区长达数年的功能,例如1970年至今。但是,采用您实际编写的函数并对其进行概括以对年份进行参数化也一样容易。因此,现在进行归纳实际上不再是任何道德/明智的做法,而是做您所做的事情,然后在需要时以及何时需要时归纳。

但是,如果您知道妻子为什么要查看此DST列表,那么您将获得知情的意见,即她是否可能在2019年再次提出相同的问题,如果可以,那么是否可以通过给出她可以调用而无需重新编译的函数。完成分析后,“应该推广到最近几年”这个问题的答案可能是“是”。但是您自己创建了另一个问题,那就是将来的时区数据只是临时的,因此如果她在2019年运行它今天,她可能会或可能不会意识到这正在为她提供最好的猜测。因此,您仍然必须编写一些不太通用的功能不需要的文档(“数据来自时区数据库等等,这是查看其推送更新等等的策略的链接”)。如果您拒绝特殊情况,那么您一直都在这样做,她无法继续她需要2018年数据的任务,因为对2019年有些废话甚至都不在乎。

不要因为没有开玩笑就正确地思考而去做困难的事情,只是因为一个玩笑告诉你。它有用吗?对于这样有用的程度,它是否足够便宜?


0

这很容易得出结论,因为架构宇航员定义的可重用性是个大问题。

应用程序开发人员创建的几乎所有的代码是非常特定领域。这不是1980年。几乎所有值得麻烦的事情都已经在框架中。

抽象和约定需要文档和学习努力。为此,请停止创建新的。(我在看着,JavaScript的人!)

让我们沉迷于令人难以置信的幻想,即您已经找到了真正应该包含在选择框架中的东西。您不能像平时那样随意编写代码。哦,不,您不仅需要针对预期用途的测试范围,而且还需要针对预期用途的偏离,所有已知的边缘情况,所有可想象的故障模式,诊断用的测试用例,测试数据,技术文档,用户文档,版本管理,支持脚本,回归测试,变更管理...

您的老板愿意为所有这些钱付款吗?我要说不。

抽象是我们为灵活性付出的代价。它使我们的代码更加复杂且难以理解。除非灵活性能够满足当前的实际需求,否则就不要这样做,因为YAGNI。

让我们看一下我刚才要处理的真实示例:HTMLRenderer。当我尝试渲染到打印机的设备环境时,缩放比例不正确。我花了整整一整天的时间发现,默认情况下它使用的是GDI(不扩展)而不是GDI +(可以,但不抗锯齿),因为在我发现之前,我必须经过两个程序集的六个间接级别可以做任何事情的代码。

在这种情况下,我会原谅作者。抽象实际上是必需的,因为这针对五个非常不同的呈现目标的框架代码:WinForms,WPF,dotnet Core,Mono和PdfSharp。

但这仅强调了我的观点:您几乎可以肯定没有针对针对多个平台的极端复杂的工作(使用样式表进行HTML渲染),并声称在所有平台上都具有高性能。

您的代码几乎可以肯定是另一个数据库网格,其中包含仅适用于您的雇主的业务规则和仅适用于您所在州的非销售应用程序的税收规则。

所有这些间接解决了一个问题,你没有,使你的代码太多难以阅读,其大量增加了维修成本,是一个巨大的伤害到你的雇主。幸运的是,应该抱怨的人无法理解您对他们的所作所为。

一个反驳的观点是,这种抽象支持测试驱动的开发,但是我认为TDD也是个难题,因为它假定业务对其需求有清晰,完整和正确的理解。TDD非常适合NASA和医疗设备和自动驾驶汽车的控制软件,但对其他所有人来说太贵了。


顺便说一句,不可能预测世界上所有的夏令时。尤其是以色列,每年都有大约40个过渡地点,到处都是,因为我们不能让人们在错误的时间祈祷,而上帝也没有节省时间。


尽管我同意您所说的关于抽象和学习努力的观点,尤其是当它针对被称为“ JavaScript”的可憎事物时,我还是非常不同意第一句话。可重用性可能发生在许多层次上,可能会变得太低了,并且可丢弃的程序员可以编写可丢弃的代码。但也有人谁是不够理想化至少宗旨,在可重用代码。如果您没有看到这样做可能带来的好处,那么对您来说很可惜。
Marco13 '18 / 12/1

@ Marco13-由于您的反对是合理的,因此我将扩大此范围。
Peter Wone

-3

如果至少使用Java 8,则应编写WorldTimeZones类以提供本质上似乎是时区的集合。

然后向WorldTimeZones类添加一个filter(Predicate filter)方法。通过提供lambda表达式作为参数,调用者可以过滤所需的任何内容。

本质上,单一过滤器方法支持对传递给谓词的值中包含的任何内容进行过滤。

或者,在您的WorldTimeZones类中添加一个stream()方法,该方法在被调用时会生成时区流。然后,调用程序可以根据需要进行过滤,映射和缩小,而无需您编写任何专门知识。


3
这些是很好的概括思想,但是此答案完全没有回答问题中所要求的内容。问题不是关于如何最好地概括解决方案,而是关于概括的界限以及如何从伦理上权衡这些考虑因素。
maple_shaft

因此,我要说的是,应根据创建的复杂性对它们支持的用例数量进行权衡。支持一个用例的方法没有支持20个用例的方法有价值。另一方面,如果您只知道一个用例,那么需要5分钟的时间来编写代码-继续吧。通常,支持特定用途的编码方法会告知您概括的方式。
Rodney P. Barbati
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.