Questions tagged «coupling»

9
我更改了一种方法签名,现在有25,000多个错误。现在怎么办?
我最近开始了新工作,正在处理一个非常大的应用程序(15M loc)。在我之前的工作中,我们有一个类似的大型应用程序,但是(不管是好是坏)我们使用OSGi,这意味着该应用程序被分解为许多微服务,这些微服务可以独立地更改,编译和部署。新的应用程序只是一个大型代码库,可能包含几个.dll。 所以我需要更改此类的界面,因为这是老板要求我执行的操作。最初,他们在编写时就采用了一些假设,但并不能很好地概括其概论,并且有一段时间以来,他们一直在避免重构问题,因为它是如此紧密地耦合在一起。我更改了界面,现在有25000多个错误。有些错误是在具有重要发音的类(如“ XYZPriceCalculator”)中重新产生的不应该打破。但是在解决所有错误之前,我无法启动应用程序来检查其是否正常运行。而且许多单元测试要么直接引用该接口,要么耦合到引用该接口的基类,因此,仅对其进行修复本身就是一项巨大的任务。另外,我真的不知道所有这些部分是如何组合在一起的,因此即使我可以开始学习,但我真的不知道如果事情破裂了会是什么样。 在上一份工作中,我从未真正遇到过这样的问题。我该怎么办?

11
如何避免级联重构?
我有一个项目。在此项目中,我希望对其进行重构以添加功能,并且对项目进行重构以添加功能。 问题是,当我完成后,原来我需要做一个小的接口更改以适应它。所以我做了改变。然后,就新类而言,无法使用其当前接口来实现消费类,因此它也需要一个新接口。现在已经三个月了,我不得不解决了几乎不相关的无数问题,而且我正在寻找解决从现在开始经过一年规划的问题,或者只是因为困难而列为无法解决的问题,然后再进行编译再次。 将来如何避免这种级联重构?这仅仅是我以前的课程互相依赖的一种征兆吗? 简要编辑:在这种情况下,重构就是功能,因为重构增加了特定代码的可扩展性并减少了一些耦合。这意味着外部开发人员可以做更多的事情,而这正是我想要提供的功能。因此,原始重构本身不应该是功能上的更改。 我五天前承诺的更大修改: 在开始重构之前,我有一个带有接口的系统,但是在实现中,我只是简单地dynamic_cast通过了所有可能的实现。显然,这意味着您不能一方面从接口继承,其次,对于没有实现访问权即可实现此接口的任何人,这都是不可能的。因此,我决定要解决此问题并开放供公众使用的界面,以便任何人都可以实施,而实施该界面是整个合同所必需的-显然是一项改进。 当我发现并用火杀死所有地方时,我发现一个地方被证明是一个特殊的问题。它取决于所有各种派生类的实现细节以及已经实现但在其他地方更好的重复功能。它可以代替公共接口来实现,而可以重新使用该功能的现有实现。我发现它需要特定的上下文才能正常运行。粗略地说,调用先前的实现看起来像 for(auto&& a : as) { f(a); } 但是,要获得此上下文,我需要将其更改为更类似的内容 std::vector<Context> contexts; for(auto&& a : as) contexts.push_back(g(a)); do_thing_now_we_have_contexts(); for(auto&& con : contexts) f(con); 这意味着对于以前曾经是其中一部分的所有操作f,其中一些操作需要成为g无需上下文的新功能的一部分,而其中某些操作则需要由现在延迟的一部分组成f。但是,并非所有方法都f调用需要或需要此上下文-其中一些需要通过单独的方式获得的独特上下文。因此,对于f最终调用的所有内容(大致来说,几乎所有内容),我必须确定他们需要的上下文(如果有的话),应该从哪里获取以及如何将它们从旧f分为新f与新g。 这就是我最终所处位置的方式。我一直坚持下去的唯一原因是因为无论如何我都需要这种重构。

6
为什么“功能与数据之间的紧密耦合”不好?
我在第40页的“ Clojure的喜悦 ”中找到了这句话。32岁,但上周晚饭时有人对我说了同样的话,我在其他地方也听说过: [A]面向对象编程的缺点是函数和数据之间的紧密耦合。 我理解为什么不必要的耦合在应用程序中不好。我也很高兴地说,即使在面向对象的编程中,也应避免避免可变状态和继承。但是我看不到为什么在类上粘贴函数本质上是不好的。 我的意思是,向类添加功能似乎就像在Gmail中标记邮件,或将文件粘贴在文件夹中。这是一种组织技巧,可帮助您再次找到它。您选择一些条件,然后将类似的东西放在一起。在OOP之前,我们的程序几乎是文件中方法的一大包。我的意思是,您必须将函数放在某个地方。为什么不组织它们? 如果这是对类型的公开攻击,为什么他们不只是说将输入和输出的类型限制为一个函数是错误的?我不确定是否可以同意这一点,但是至少我对pro和con类型安全性论点很熟悉。在我看来,这似乎是一个主要的问题。 当然,有时人们会错误地将功能放在错误的类上。但是与其他错误相比,这似乎是一个很小的麻烦。 因此,Clojure具有名称空间。在OOP中将函数粘贴在类上与在Clojure中将函数粘贴在命名空间中有什么不同,为什么这么糟?请记住,类中的函数不一定只在该类的成员上运行。看一下java.lang.StringBuilder-它可以对任何引用类型进行操作,也可以通过自动装箱对任何类型进行操作。 PS此引用引用了一本书,但我没有读过:《Leda中的Multiparadigm编程》:Timothy Budd,1995年。

5
您应该在哪里放置常数,为什么?
在大多数大型应用程序中,通常只有几个“常量”位置: 一类用于GUI和内部内容的内容(制表页标题,组框标题,计算因子,枚举) 一类用于数据库表和列(此部分是生成的代码)以及它们的可读名称(手动分配) 一类应用程序消息(日志,消息框等) 在这些类中,常量通常分为不同的结构。在我们的C ++应用程序中,仅在.h文件中定义常量,而在.cpp文件中分配值。 优点之一是,所有琴弦等都放在一个中央位置,每个人都知道在必须更改某些内容时可以在哪里找到它们。 这尤其是项目经理在人们来来往往时喜欢的东西,这样每个人都可以更改这些琐碎的事情,而不必深入研究应用程序的结构。 另外,您可以轻松地一次更改相似的“组框” /“选项卡页”等的标题。另一个方面是,您可以仅打印该类并将其交给非程序员,该非程序员可以检查字幕是否直观,向用户发送的消息是否太详细或太混乱等。 但是,我看到了某些缺点: 每个类都与常量类紧密耦合 添加/删除/重命名/移动常量需要重新编译至少90%的应用程序(注意:至少在C ++中,不需要更改值)。在我们的一个带有1500个类的C ++项目中,这意味着大约7分钟的编译时间(使用预编译的标头;如果没有它们,则大约为50分钟),再加上大约10分钟的链接到某些静态库。 通过Visual Studio编译器构建速度优化的发行版最多需要3个小时。我不知道是否大量的类关系是源,但也有可能。 您会被迫将临时硬编码的字符串直接转化为代码,因为您想非常快速地测试某项内容,并且不想仅等待15分钟来进行该测试(并且可能以后每次测试)。每个人都知道“以后我会解决”的想法。 在另一个项目中重用一个类并不总是那么容易(主要是由于其他紧密的耦合,但是常量处理并没有使它更容易。) 您将在哪里存储这样的常量?为了使您的项目经理相信还有更好的概念也符合上面列出的优点,您还会提出什么论点? 随意给出特定于C ++或独立的答案。 PS:我知道这个问题是主观的,但是老实说,我不知道有比这个网站更好的地方。 有关此项目的更新 关于编译时间,我有个新闻: 在Caleb和gbjbaanb的帖子中,有空时,我将常量文件拆分为其他几个文件。我最终还将我的项目分成几个库,现在可以轻松得多。在发布模式下进行编译显示,包含数据库定义(表,列名等-超过8000个符号)并建立了某些哈希值的自动生成的文件在发布模式下造成了巨大的编译时间。 现在,通过停用包含DB常量的库的MSVC优化器,我们可以将发布模式下的项目(几个应用程序)的总编译时间从最多8小时减少到不到一小时! 我们还没有找到为什么MSVC很难优化这些文件,但是现在,由于我们不再只依赖夜间构建,因此此更改减轻了很多压力。 这个事实以及其他好处,例如紧密耦合少,可重用性更好等,也表明花时间拆分“常量”并不是一个坏主意;-) 更新2 由于这个问题仍然受到关注: 这是过去几年中我一直在做的事情: 将每个常量,变量等完全放在与其相关的范围内:如果仅在单个方法中使用常量,则可以在该方法中定义它。如果单个类对此感兴趣,请将其保留为该类的私有实现细节。这同样适用于名称空间,模块,项目,公司范围。我还将相同的模式用于助手功能等。(如果您开发一个公共框架,这可能不会100%适用。) 这样做可以提高可重用性,可测试性和可维护性,使您不仅花费更少的时间(至少在C ++中),而且花费在错误修复上的时间也更少,这使您有更多的时间实际开发新功能。同时,开发这些功能的速度会更快,因为您可以更轻松地重用更多代码。这在一定程度上胜过中央常量文件可能具有的任何优势。 如果您想了解更多,请特别看一下接口隔离原则和单一职责原则。 如果您同意,请支持Caleb的回答,因为此更新基本上是他所说的更一般的看法。

3
依赖注入如何增加耦合?
在有关依赖项注入的Wikipedia页面上,劣势部分告诉我们: 依赖注入通过要求子系统的用户满足该子系统的需求来增加耦合。 链接到反对依赖注入的文章。 依赖注入使类使用接口而不是具体的实现。那应该导致耦合减少,不是吗? 我想念什么?依赖注入如何增加类之间的耦合?

3
如何正确地将UI与Pyqt / Qt应用上的逻辑分离?
过去,我已经阅读了很多有关该主题的文章,并观看了Bob叔叔的有趣的演讲。但是,我始终很难正确地构建桌面应用程序,并区分在UI端和逻辑端应该承担哪些责任。 良好做法的简短摘要就是这样的。您应该设计与UI分离的逻辑,以便无论哪种后端/ UI框架,都可以(理论上)使用库。基本上,这意味着UI应该尽可能地虚设,繁重的处理应该在逻辑端进行。否则,我可以在控制台应用程序,Web应用程序或桌面应用程序上真正使用我的漂亮库。 此外,鲍伯叔叔建议对使用哪种技术会给您带来很多好处(良好的界面)的不同讨论,这种递延概念使您可以将经过良好测试的实体高度去耦,这听起来不错,但仍然很棘手。 因此,我知道这个问题是一个相当广泛的问题,已经在整个互联网上讨论了很多次,还以大量好书进行了讨论。因此,为了从中获得好处,我将发布一个非常小的虚拟示例,尝试在pyqt上使用MCV: import sys import os import random from PyQt5 import QtWidgets from PyQt5 import QtGui from PyQt5 import QtCore random.seed(1) class Model(QtCore.QObject): item_added = QtCore.pyqtSignal(int) item_removed = QtCore.pyqtSignal(int) def __init__(self): super().__init__() self.items = {} def add_item(self): guid = random.randint(0, 10000) new_item = { "pos": [random.randint(50, 100), …
20 design  python  mvc  gui  coupling 

5
为什么将类型与生成器结合在一起?
我最近在Code Review上删除了我的一个Java答案,它的开始是这样的: private Person(PersonBuilder builder) { 停止。红色标志。一个PersonBuilder将建立一个Person;它知道一个人。Person类应该对PersonBuilder一无所知-这只是一个不可变的类型。您已经在此处创建了圆形耦合,其中A取决于B,而B取决于A。 该人员应仅获取其参数;愿意创建一个人而不创建它的客户应该能够做到这一点。 我被选票打了耳光,并告诉我(引用)红旗,为什么?这里的实现与Joshua Bloch在其“ Effective Java”书(项目2)中演示的形状相同。 因此,看来在Java 中实现构建器模式的一种正确方法是使构建器成为嵌套类型(尽管这不是这个问题),然后制造产品(正在构建的对象的类) )对构建器的依赖,如下所示: private StreetMap(Builder builder) { // Required parameters origin = builder.origin; destination = builder.destination; // Optional parameters waterColor = builder.waterColor; landColor = builder.landColor; highTrafficColor = builder.highTrafficColor; mediumTrafficColor = builder.mediumTrafficColor; lowTrafficColor = builder.lowTrafficColor; } https://zh.wikipedia.org/wiki/Builder_pattern#Java_example 对于相同的Builder模式,相同的Wikipedia页面对于C#具有非常不同的实现(并且更加灵活): //Represents …

4
事件驱动的编程:什么时候值得?
好的,我知道这个问题的标题几乎与何时应该使用基于事件的编程相同?但是上述问题的答案并没有帮助我确定我是否应该在遇到的特定情况下使用事件。 我正在开发一个小型应用程序。这是一个简单的应用程序,大部分功能是基本CRUD。 在发生某些事件时(修改某些数据时),应用程序必须在文件中写入所述数据的本地副本。我不确定实现此目标的最佳方法是什么。我可以: 修改数据时触发事件,并将响应(生成文件)绑定到此类事件。或者,实现观察者模式。这似乎是不必要的复杂性。 直接从修改数据的代码中调用文件生成代码。简单得多,但是依赖关系应该是这种方式似乎是错误的,也就是说,将应用程序的核心功能(修改数据的代码)与额外的特权(生成备份文件的代码)耦合起来似乎是错误的。我知道,但是,这个应用程序不会发展到那种耦合带来问题的地步。 在这种情况下最好的方法是什么?

3
解耦胜过REST中的DRY吗?
我正在构建REST API,以公开现有Java API的大部分功能。这两个API都供我的组织内部使用;我不必为外部使用而设计。我对这两种API都有影响,但是正在实现REST。Java API将继续用于本地应用程序(不会“淘汰”),但是REST API将用于重大的新开发。 一些Java API类只是数据(带有属性,getter,setter的bean)。并且至少其中一些有意义的是通过REST API以某种形式(作为数据(将被编组为XML或JSON))进行传输。例如,一个存储有关服务器计算机信息的类。对于这些数据类,我面临以下选择:我是否... 直接在REST API中公开原始Java类(或子类),或者 专门为REST API创建新的数据传输类(DTO模式)? 无论哪种方式,我都会有REST数据传输类。问题是是要注释原始文件还是创建新的文件(可能是原始文件的副本)。可能还有其他选择,但我将主要关注这两个。 #1的参数: 干(不要重复自己) 实施更快 升级REST API更容易 #2的参数: 如果REST API需要与Java API分开版本,该怎么办?(这很有可能。) 如果Java数据类发生重大更改(例如,删除属性,添加行为或更改类层次结构)怎么办?(这也有可能。) 底线是,似乎在DRY(#1)和去耦(#2)之间进行了权衡。 我倾向于从#1开始,然后如果出现问题,然后再转到#2,请遵循不构建无法证明自己需要的敏捷指导。这是一个坏主意吗; 如果我认为我最终还是会从那里开始,我应该从#2开始吗? 我的清单中是否缺少主要的论据/后果?
19 java  api  rest  coupling  dry 

8
与字符串耦合比与类方法耦合更“松散”吗?
我正在使用Swing在Java中启动一个学校小组项目。这是数据库桌面应用程序上的简单GUI。 教授给了我们去年项目的代码,以便我们了解他的工作方式。我最初的印象是代码要复杂得多,但是我想程序员在查看他们不仅编写的代码时经常会想到这一点。 我希望找到他的系统好坏的原因。(我问教授,他说我稍后再看为什么会更好,这让我不满意。) 基本上,为了避免他的可持久对象,模型(业务逻辑)和视图之间的任何耦合,所有操作都由字符串完成。存储在数据库中的可持久对象是字符串的哈希表,模型和视图彼此“订阅”,为它们所订阅的“事件”提供字符串键。 触发事件后,视图或模型会向所有订阅者发送字符串,这些订阅者将决定对该事件采取何种措施。例如,在一种视图动作侦听器方法中(我相信这只是在可持久对象上设置了bikeMakeField): else if(evt.getSource() == bicycleMakeField) { myRegistry.updateSubscribers("BicycleMake", bicycleMakeField.getText()); } 该调用最终到达Vehicle模型中的此方法: public void stateChangeRequest(String key, Object value) { ... bunch of else ifs ... else if (key.equals("BicycleMake") == true) { ... do stuff ... 这位教授说,这种处理方式比让视图简单地在业务逻辑对象上调用方法更具可扩展性和可维护性。他说,视图和模型之间没有耦合,因为它们不知道彼此的存在。 我认为这是一种较差的耦合,因为视图和模型必须使用相同的字符串才能工作。如果删除视图或模型,或在字符串中输入错误,则不会出现编译错误。这也使代码比我认为的要长得多。 我想和他讨论这个问题,但是他利用他的行业经验来反驳我这个经验不足的学生可能提出的任何论点。我缺少他的方法有什么优势吗? 为了明确起见,我想比较上面的方法,同时将视图与模型明显耦合。例如,您可以将车辆模型对象传递到视图,然后更改车辆的“制造”,请执行以下操作: vehicle.make = bicycleMakeField.getText(); 这将把目前仅用于将车辆的品牌设置在一个地方的15条代码减少为一条可读代码。(由于这种操作在整个应用程序中进行了数百次,因此我认为这将是可读性和安全性的巨大胜利。) 更新资料 我的团队负责人和我重组了框架,采用了我们希望使用静态类型进行编码的方式,并告知了教授,最后给了他一个演示。只要我们不向他求助,他是否足够慷慨地允许我们使用我们的框架,以及是否可以让我们其余的团队保持最新状态-这对我们来说似乎很公平。
18 java  coupling 

6
面向对象设计中的松耦合
我正在尝试学习GRASP,我发现了有关低耦合的解释(在第3页上),当我发现这一点时,我感到非常惊讶: 考虑addTrack一个Album类的方法,两种可能的方法是: addTrack( Track t ) 和 addTrack( int no, String title, double duration ) 哪种方法可以减少耦合?第二个则需要,因为使用Album类的类不必知道Track类。通常,方法的参数应使用基本类型(int,char ...)和java。*包中的类。 我倾向于不同意这一点。我相信addTrack(Track t)胜于addTrack(int no, String title, double duration)各种原因: 最好总是使用尽可能少的参数的方法(根据Bob叔叔的“清洁代码”,最好是无或一个,在某些情况下为2,在特殊情况下为3;超过3的需求需要重构-这些当然是建议,而不是冬青规则) 。 如果addTrack是接口的一种方法,并且要求a Track应该具有更多的信息(例如年份或类型),则需要更改接口,以便该方法应支持另一个参数。 封装破裂;如果addTrack在接口中,则它不应该知道的内部Track。 实际上,它在第二种方式中与许多参数耦合在一起。假设no参数需要被改变,从int到long,因为有超过MAX_INT轨道(或无论何种原因); 则Track必须同时更改和方法,而如果addTrack(Track track)仅更改方法,则需要Track更改。 这四个参数实际上是相互关联的,其中一些是其他因素的结果。 哪种方法更好?

3
单元测试行为,无需耦合到实现细节
Ian Cooper 在他的演讲TDD中,哪里都出错了,将Kent Beck的初衷推到了TDD中的单元测试(测试行为,而不是具体的类方法),并主张避免将测试与实现耦合。 对于行为,例如save X to some data source在具有一组典型的服务和存储库的系统中,我们如何通过存储库对服务级别的某些数据的保存进行单元测试,而又不将测试与实现细节耦合(例如调用特定方法) )?避免这种耦合实际上不值得付出某种努力/坏处吗?

5
是否可以在不增加耦合的情况下应用DRY?
假设我们有一个实现功能F的软件模块A。另一个模块B实现了与F'相同的功能。 有多种方法可以消除重复的代码: 让A使用B的F'。 让B使用A中的F。 将F放入自己的模块C中,让A和B都使用它。 所有这些选项都会在模块之间生成其他依赖关系。他们以增加耦合为代价应用了DRY原理。 据我所知,在应用DRY时,耦合总是增加或至少增加到更高的水平。软件设计的两个最基本的原则之间似乎存在冲突。 (实际上,这样的冲突并不令人惊讶。这可能是使良好的软件设计如此困难的原因。我确实感到惊讶的是,通常在介绍性文本中未解决这些冲突。) 编辑(为澄清起见):我认为F和F'的相等不只是一个巧合。如果必须修改F,则很可能必须以相同的方式修改F'。

3
通过DRY和OOD引入代码耦合
我正在寻找有关DRY与代码耦合的指南。我不喜欢复制代码,也不喜欢无关模块之间的代码耦合。因此,如果在引入重复项一年后发现完全相同的重复代码,我将重构重复代码。但是,我越来越多地体验到现实世界中更加难以预测的情况,并且在重构代码之后,出现了一些情况,需要再次分叉代码。 例如,如果我有处理汽油车,汽油SUV,电动汽车和电动SUV的代码,可以说我将重复的代码重构为“汽油”层次结构和“电动”层次结构,它们均从“车辆”层次结构中衍生而来。到目前为止,一切都很好。然后,我的公司推出了混合动力汽车和混合动力汽车Semi-需要对我的原始等级本身进行核心更改。可能需要在汽油和电气层次之间进行“组合”。 显然,代码重复是不好的,因为它增加了实现上述所有产品共有的更改所花费的时间。但是重构通用代码使引入产品特定的变体同样困难,并且当人们不得不找到代码行来修复错误时,会导致很多“类跳转”-上级父类中的一项更改可以触发所有后代之间的回归错误。 如何在DRY与有害代码耦合之间达到最佳平衡?
14 design  dry  coupling 

5
如何使用依赖注入并避免时间耦合?
假设我有Service通过构造函数接收依赖项的,但还需要使用自定义数据(上下文)进行初始化,然后才能使用它: public interface IService { void Initialize(Context context); void DoSomething(); void DoOtherThing(); } public class Service : IService { private readonly object dependency1; private readonly object dependency2; private readonly object dependency3; public Service( object dependency1, object dependency2, object dependency3) { this.dependency1 = dependency1 ?? throw new ArgumentNullException(nameof(dependency1)); this.dependency2 = dependency2 …

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.