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


亲爱的。你能在世界各地打印2018年的所有日光节约量吗?安慰?我需要检查一下。


我非常高兴,因为那是我一直在等待自己的Java经验并提出的想法:

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

class App {
    void dayLightSavings() {
        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) {
        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.DESTROY再用Action.REBUILD来称呼它,不是吗?

但是对城市采取行动对我来说还不够,takeActionOnGeographicArea(Action action, GeographicalArea GeographicalArea)怎么样?毕竟,我不想打电话给我:

takeActionOnCity(Action.DESTORY, City.BAGHDAD);


然后

takeActionOnCity(Action.DESTORY, City.ERBIL);


可以做到:

takeActionOnGeographicArea(Action.DESTORY, Country.IRAQ);


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

评论

可能的经验法则可以用来复制成本,而可以节省代码重复使用

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

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

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

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

#1 楼

完全是乌龟。

在这种情况下还是抽象。

良好实践编码是可以无限应用的东西,在某些时候,您正在为为了抽象起见,这意味着您已经走得太远了。很难找到一条线,因为这很大程度上取决于您的环境。

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

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

我之所以提到上面的原因是因为您已经陷入困境这个陷阱:


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


“她可能会说“这不是当前的业务需求。这是对未来业务需求的猜测。一般而言,不要以猜测为基础,而只是开发当前需要的内容。

但是上下文适用于此处我不认识你的妻子,也许你准确地估计了她实际上会想要的,但是你仍然应该与顾客确认这确实是他们想要的,否则你会花时间去开发ping永远不会使用的功能。

我如何知道要实现哪种架构?

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

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

我如何知道何时进一步抽象代码?

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

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)怎么样?


相同的问题在这里弹出:


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

如果您可以肯定地对所有问题回答“是”, ree,则必须进行抽象。

评论


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

–克里斯蒂安·绍尔(Christian Sauer)
18年11月28日在8:39

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

–Rune
18-11-28在10:06



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

–FilipMilovanović
18年11月28日在12:10

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

–FilipMilovanović
18年11月28日在12:14

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

–更
18年11月29日在13:31



#2 楼

练习
这是软件工程SE,但是制作软件比工程学要复杂得多。没有通用算法可以用来确定多少可重用性就可以遵循或测量。像其他任何东西一样,设计程序的实践越多,您所获得的效果就越好。您将对“足够”的内容有更好的感觉,因为当您过多或太少地参数化时,您都会看到问题出在哪里,以及问题出在哪里。
现在这不是很有用,那准则?
回头看看您的问题。有很多“她可能会说”和“我会说”。关于未来需求的理论上有很多说法。人类在预测未来时大吃一惊。而你(很可能)是人类。软件设计的压倒性问题是试图说明您不知道的未来。
指南1:您将不再需要它
停下来。
指南2:成本/收益
很酷,那个小程序带给你的很多东西都不会出现-而且肯定不会像您想象的那样出现。可能要写几个小时?那么,如果您的妻子回来找这些东西怎么办?最坏的情况是,您花了几个小时来整理另一个程序。对于这种情况,没有太多时间使该程序更灵活。而且不会增加运行时速度或内存使用量。但是非平凡的程序有不同的答案。不同的场景有不同的答案。在某些时候,即使未来的讲演技能不完善,成本显然也不值得。
指南3:关注常量
回头看看这个问题。在您的原始代码中,有很多恒定的整数。 20181。恒定整数,恒定字符串...它们是最可能需要非恒定的东西。更好的是,它们仅需要一点时间即可进行参数化(或至少定义为实际常数)。但是要提防的另一件事是行为不断。以System.out.println为例。这种关于使用的假设往往会在将来发生变化,并且修复起来的成本往往很高。不仅如此,但这样的IO会使功能不纯(以及时区有所获取)。参数化该行为可以使函数更纯净,从而提高灵活性和可测试性。以最小的成本获得巨大收益(尤其是如果您默认使用System.out进行重载)。

评论


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

– Telastyn
18年11月27日在4:50

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

–布朗博士
18-11-27在4:55



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

–Jörg W Mittag
18年11月27日在7:21

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

–更
18-11-27在7:52



@Telastyn:我建议将问题扩展为“这将永远不会改变,并且在不命名常量的情况下,代码的意图是否易于理解?”即使对于永不更改的值,也可能为了保持可读性而命名它们。

–更
18年11月27日在8:12

#3 楼

首先:出于安全考虑,没有任何软件开发人员会出于任何原因而未通过授权令牌的情况下编写DestroyCity方法。

我也可以编写任何具有明显智慧的命令,而不必将其应用于其他情况。

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

决定是否已硬编码到位都没有关系,或推迟到另一层。在某种程度上,有一段用某种语言编写的代码既知道将要销毁的内容又是如何指示它的代码。

可以在同一对象文件destroyCity(xyz)中,也可以在一个目标文件中。配置文件:destroy {"city": "XYZ"}",或者它可能是用户界面中的一系列单击和按键。

第三次:


亲爱的..您可以整天打印吗控制台中的2018年全球节能灯?我需要检查一下。


是一组与以下完全不同的要求:


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


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

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

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

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

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

开始时的建议意味着,您的功能无权做出的决定应作为论据。问题是DestroyBaghdad函数实际上可能是具有该权限的函数。

评论


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

–李
18年11月28日在9:10

#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实例。可以将其放在待办事项列表中,并在适当的项目管理委员会会议上确定优先级。

评论


您(向OP致辞)肯定不应该添加格式字符串,因为您不应打印任何内容。关于此代码的一件事是绝对禁止其重复使用,因为它可以打印。当它们离开DST时,应返回区域或区域映射。 (尽管为什么它只标识何时关闭DST,而不标识何时DST,但我不明白。它似乎与问题说明不符。)

–大卫·康拉德(David Conrad)
18年11月28日在18:00

要求是打印到控制台。您可以按照我的建议通过将输出流作为参数传递来消除紧密耦合

–伊万
18年11月28日在18:21

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

–大卫·康拉德(David Conrad)
18年11月28日在18:52



输出流是消费者

–伊万
18年11月28日在20:07

不,OutputStream不是使用者。

–大卫·康拉德(David Conrad)
18年11月28日在20:52

#5 楼

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

对于您提供的示例这样简单的事情,您的初始实现是好的,因为它很小,但是这种简单的设计将变得难以理解。如果尝试将过多的功能灵活性(而不是设计灵活性)塞入一个过程中,则会很脆弱。以下是我对设计复杂系统以提高可理解性和灵活性的首选方法的解释,我希望它将证明我的意思。我不会将这种策略用于可能在一个过程中用少于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应该使您能够执行所需的任何操作,以便让它担心满足您的请求。

委派,而不是“ Control”

现在,您的实施者级别是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);            
    }
}


如果您想变得疯狂起来并实施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库中不支持此设计的类,因此您可以直接使用ZonedDateTime的API。这里的想法是,每个计算都封装在其自己的对象中。它不假定应运行多少次或应如何格式化结果。它只与执行最简单的计算形式有关。这使得它既易于理解又可以灵活地进行更改。同样,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♦
18-11-28在13:46

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

– Stuporman
18年11月28日在16:49

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

– Stuporman
18年11月28日在16:53

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

– Stuporman
18年11月28日在18:06

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

–斯蒂芬·J
18年11月29日在23:08

#6 楼

经验,领域知识和代码审查。

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


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

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

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

使用代码审查,参数化过高和过高的可能性将更大在产品代码变成产品代码之前就被捕获了,因为您的同行(希望)在域和总体上都具有独特的经验和观点。


那,新开发人员通常不会拥有这些Spidey Sense或经验丰富的同龄人团体可以立即依靠。而且,即使是经验丰富的开发人员也可以从基本准则中受益,以指导他们提出新的要求或经历迷雾笼罩的日子。因此,以下是我建议的建议:


从天真的实现开始,以最小的参数化(显然,包括您已经知道需要的任何参数... )

删除幻数和字符串,将它们移到配置和/或参数中
将因子“大”方法分解为名称更小的较小方法
重构高度冗余的方法(如果方便)变成一个共同的分母,参数化差异。

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

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

评论


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

–科雷·图吉(Koray Tugay)
18年11月27日在17:45

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

– svidgen
18-11-27在20:28



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

– svidgen
18-11-27在20:31



因此,您不同意我的问题中引用的报价,对吗?应该注意的是,没有经过道德培训的软件工程师会同意编写DestroyBaghdad程序。如果您和纳撒尼尔·波伦斯坦在房间里,您会认为他确实取决于他,而他的说法是不正确的?我的意思是,很多人都在花时间和精力来回答问题,这很美丽,但是我看不到任何具体答案。 Spidey-senses,代码审查。。但是,takeActionOnCity(行动动作,城市城市)更好的答案是什么?空值?

–科雷·图吉(Koray Tugay)
18年11月27日在20:52

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

–罗安
18年11月29日在12:46

#7 楼

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

与您一样可重复使用的用户1要求他们成为

基本上这是一个判断电话-是否设计和维护抽象的成本将由成本(=时间和精力)偿还,这将使您节省下来。


请注意“下来”一词:这里有一个回报机制,所以这将取决于您将进一步使用此代码多少。例如:


这是一个一次性的项目,还是会在长期内逐步改善?
您对设计有信心吗?是否必须为下一个项目/里程碑废弃或以其他方式进行大幅度更改(例如尝试其他框架)?


预期的收益还取决于您对未来的预测能力(对应用程序的更改) )。有时,您可以合理地看到您的应用程序将要使用的场所。更多的时间,您认为可以,但实际上却不能。经验法则是YAGNI原理和三个法则-都强调现在就了解您的知识。



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

#8 楼

您可以按照一个清晰的过程进行操作:


为单个功能编写失败的测试,该功能本身就是一件“事情”(即,不要对功能进行任意分割,而实际上没有一半可以
写出绝对的最小代码以使其通过绿色,而不是更多行。
漂洗并重复。
(如有必要,请进行不懈的重构,这很容易,因为它很棒测试覆盖率。)

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

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

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

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

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

评论


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

– dwizum
18年11月28日在21:29

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

– AnoE
18年11月29日在11:34

#9 楼

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


可重用的代码,因为它是基本的基本内容。
可重用的代码是因为它具有可在任何地方使用的参数,覆盖和挂钩。

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

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

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

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

评论


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

–科雷·图吉(Koray Tugay)
18年11月29日在23:38

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

–Warbo
18年11月30日在7:21

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

–Warbo
18-11-30在7:34

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

–科雷·图吉(Koray Tugay)
18年11月30日在19:11

我喜欢这种差异。这并不总是很清楚,并且总是有“边界案例”。但总的来说,我还是纯功能的,底层的“构建块”(通常以静态方法的形式)的爱好者,而与此相反,决定“配置参数和挂钩”通常是在这里您必须建立一些要求合理的结构。

– Marco13
18/12/1在21:24

#10 楼

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

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

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

关于问题中的示例:假设方法

void dayLightSavings()


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

public void dayLightSavings()

可以如您在您的演示中所示实现回答。现在,有人想用年份来参数化它。因此,您可以添加方法
public void dayLightSavings(int year)

,并将原始实现更改为

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


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

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


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

; dr:

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

评论


在我看来,调用dayLightSavings(2018)的dayLightSavings()似乎不是一个好主意。

–科雷·图吉(Koray Tugay)
18/12/1在22:31

@KorayTugay当最初的要求是打印“ 2018年的夏时制”时,就可以了。实际上,这正是您最初实现的方法。如果它应该显示“当年的夏令时,那么您当然会调用dayLightSavings(computeCurrentYear());. ...

– Marco13
18/12/1在22:44

#11 楼

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

如果您希望只在一个地方调用您的方法,那么它应该只具有调用已知的参数。网站,并且不适用于此方法。

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

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

#12 楼

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

您的示例仅取决于

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


所以从理论上讲它可以高度重用。

在实践中,我认为您不会再有需要此代码的用例,因此遵循yagni原理,如果有的话,我不会使其重用。

不超过3个需要此代码的项目。

可重用性的其他方面是易用性和文档编制,它们与Test Driven Development紧密相关:如果您有一个简单的单元测试来演示/记录了将通用模块轻松用作lib用户的编码示例的方法。

#13 楼

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

成为一个好的程序员意味着能够预测未来。

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

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

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

(最近的例子:上周给我提供了一个动作规范系统应该在发生过期后的整整2天之内。因此,我当然将2天的时间作为参数。本周,业务人员很高兴,因为他们将要求进行增强!我很幸运:这是一个简单的更改,我猜想很有可能是这样。通常很难判断。但是仍然值得尝试预测,经验通常是一个很好的指南。)

#14 楼

首先,“我怎么知道我的方法应该是可重用的?”的最佳答案是“经验”。这样做数千次,您通常会得到正确的答案。但是作为预告片,我可以为您提供最后一个

其中许多答案都有特定的建议,我想给出一些更通用的方法。

正如一些答案所指出的那样,普遍性是昂贵的,但实际上并非如此,并非总是如此。

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

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

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

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

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

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

评论


那么应该有其他人这样做10000次,那么为什么我可以做10000次并从别人那里学到经验呢?因为每个人的答案都不同,所以经验丰富的答案不适用于我吗?此外,您的客户还会告诉您应该寻求多少灵活性以及概括的层次。这是什么世界?

–科雷·图吉(Koray Tugay)
18年11月30日在19:29

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

–Cort Ammon
18年11月30日在19:45

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

–Cort Ammon
18-11-30在19:46



因此,您是否同意我的第一种方法是正确的方法,而不是参数化年份,而是首先硬编码2018年? (顺便说一句,我认为这并不是真正地在听我的客户,垃圾示例。那是在了解您的客户。。即使得到了Oracle的支持,当她说我需要一张日光清单时,也没有什么微妙的信息可听。变更为2018年。)感谢您的宝贵时间并回答问题。

–科雷·图吉(Koray Tugay)
18年11月30日在19:50

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

–Cort Ammon
18年11月30日在21:40

#15 楼


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


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

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

这也导致对维护进行编码,以防止随时间的变化:由于现代建筑方法和无处不在的雷达,现在有很多城市都不属于这两种方法。

开发和测试一种完全通用的方法意味着在您同意摧毁一个城市之前,摧毁任何一个城市都是一种极其无效的方法。在没有经过证明的要求的情况下,没有受过道德训练的软件工程师会试图将问题推广到要求其工作量超出其雇主/客户实际需要支付的数量级的程度。

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

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

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

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

但是,如果您知道为什么您的妻子想检查一下此DST列表,那么您将对她是否有可能在2019年再次提出相同的问题有一个知情的意见,如果是,那么是否可以通过赋予她可以调用的功能来使自己摆脱困境,而无需重新编译它。完成分析后,“应该推广到最近几年”这个问题的答案可能是“是”。但您自己会遇到另一个问题,那就是将来的时区数据只是临时的,因此如果她今天将其运行到2019年,她可能会或可能不会意识到这正在为她提供最佳猜测。因此,您仍然必须编写一些不太通用的功能不需要的文档(“数据来自时区数据库等等,这是查看其推送更新等等政策的链接”)。如果您拒绝特殊情况,那么您一直都在这样做,她无法继续她需要2018年数据的任务,因为对2019年有些废话,她甚至都不在乎。 br />
不要因为没有开玩笑就正确地去做困难的事情,只是因为开玩笑告诉你。它有用吗?这样有用的程度够便宜吗?

#16 楼

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

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

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

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

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

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

让我们看一下我不得不处理的真实示例:HTMLRenderer。当我尝试渲染到打印机的设备环境时,缩放比例不正确。我花了整整一天的时间发现,默认情况下它使用的是GDI(不扩展)而不是GDI +(可以,但不抗锯齿),因为我不得不在发现两个程序集时经历了六个间接级别可以执行任何操作的代码。在这种情况下,我会原谅作者。抽象实际上是必需的,因为这是针对五个非常不同的呈现目标的框架代码:WinForms,WPF,dotnet Core,Mono和PdfSharp。

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

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

所有间接解决了您没有的问题,并使代码更难阅读,这极大地增加了维护成本,并且对雇主造成极大的伤害。幸运的是,应该对此抱怨的人无法理解您对他们的所作所为。

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


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

评论


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

– Marco13
18/12/1在21:41



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

– Peter Wone
18/12/2在23:22

#17 楼

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

然后向WorldTimeZones类添加filter(Predicate filter)方法。这样,调用者就可以通过传递lambda表达式作为参数来对他们想要的任何内容进行过滤。

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

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

评论


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

– Maple_shaft♦
18年11月28日在13:34

因此,我要说的是,应该根据创建的复杂性来修改它们所支持的用例的数量,来权衡它们。支持一个用例的方法没有支持20个用例的方法有价值。另一方面,如果您只知道一个用例,则需要5分钟的时间来编写代码-继续吧。通常,支持特定用途的编码方法会告知您概括的方式。

–罗德尼·巴尔巴蒂(Rodney P. Barbati)
18年11月29日在21:26