记录器在参数列表中的位置应该是什么[关闭]


12

在我的代码中,我通过构造函数的参数列表为许多类注入了记录器

我注意到我将其随机放置:有时它是列表中的第一个,有时是最后一个,有时是介于两者之间

你有什么喜好吗?我的直觉说,在这种情况下,一致性是有用的,而我个人的偏好是将其放在首位,这样在丢失时更容易被注意到,而在丢失时更容易跳过。

Answers:


31

记录仪是我们所谓的“跨领域关注点”。它们产生于面向方面编程等技术。如果您有一种用属性修饰类或执行一些代码编织的方法,那么这是一种在保持对象和参数列表“纯净”的同时获得日志记录功能的好方法。

您可能希望传递记录器的唯一原因是,如果您想指定不同的记录实现,但是大多数记录框架都具有灵活性,可以让您针对不同的记录目标进行配置。(日志文件,Windows事件管理器等)

由于这些原因,我宁愿使日志记录成为系统的自然组成部分,而不是将记录器传递给每个类以进行记录。因此,我通常要做的是引用适当的日志记录命名空间,并在类中使用logger。

如果您仍想传递记录器,则我希望将其设为参数列表中的最后一个参数(如果可能,使其成为可选参数)。将其作为第一个参数没有多大意义。第一个参数应该是最重要的参数,与类的操作最相关的参数。


11
+!使记录器远离构造函数参数;我更喜欢静态记录器,所以我知道每个人都在使用相同的东西
Steven A. Lowe

1
@ StevenA.Lowe注意,如果您不小心使用静态记录器,可能会导致其他问题(例如,C ++中的静态初始化顺序严重失败)。我同意将记录器作为可全局访问的实体具有其魅力,但是应该仔细评估这种设计是否以及如何适合整个体系结构。
ComicSansMS 2015年

@ComicSansMS:当然,还有线程问题等。只是个人喜好-“尽可能简单,但不简单”;)
Steven A. Lowe 2015年

拥有静态记录器可能是一个问题。这使依赖注入变得更加困难(除非您是指由DI容器实例化的单例记录器),并且,如果您想更改自己的体系结构,可能会很痛苦。例如,现在,我正在使用Azure函数,这些函数将记录器作为参数传递给每个函数执行,因此我很遗憾将记录器传递给构造函数。
Slothario '19

@Slothario:这就是静态记录器的美。无需任何注射。
罗伯特·哈维

7

在具有函数重载的语言中,我认为参数越可能是可选的,则它应该越正确。当您创建过载丢失的地方时,这将创建一致性:

foo(mandatory);
foo(mandatory, optional);
foo(mandatory, optional, evenMoreOptional);

在函数式语言中,反向操作更有用-选择默认值的可能性越大,则默认值应该越远。通过简单地对其应用参数,就可以更轻松地使函数专用化:

addThreeToList = map (+3)

但是,正如在其他答案中提到的那样,您可能不想在系统中每个类的参数列表中显式传递记录器。


3

您绝对可以花很多时间对这个问题进行过度设计。

对于具有规范记录实现的语言,只需在每个类中直接实例化规范记录器即可。

对于没有规范实现的语言,请尝试查找并遵循日志框架外观框架。slf4j是Java中的不错选择。

我个人宁愿坚持一个具体的日志记录实现,然后将所有内容发送到syslog。所有优秀的日志分析工具都能够将来自多个应用服务器的sysout日志合并到一个综合报告中。

当函数签名包含一个或两个依赖服务以及一些“真实”参数时,我将依赖关系放在最后:

int calculateFooBarSum(int foo, int bar, IntegerSummationService svc)

由于我的系统往往只有五个或更少的此类服务,因此我始终确保在所有功能签名中以相同的顺序包含这些服务。字母顺序和其他顺序一样好。(此外:保持这种方法学方法进行互斥处理也将减少产生死锁的机会。)

如果您发现自己在整个应用程序中注入了十几个依赖关系,则可能需要将系统拆分为单独的子系统(敢于说微服务吗?)。


对我来说,使用属性注入来调用calculateFooBarSum似乎很尴尬。
一个富

2

记录器有点特殊情况,因为它们实际上必须在任何地方都可用。

如果您决定要将记录器传递到每个类的构造函数中,那么您绝对应该为操作方法设置一致的约定(例如,始终第一个参数,始终通过引用传递,构造函数初始化列表始终会开始使用m_logger(theLogger)等)。有一天,整个代码库中将要使用的所有内容都将从一致性中受益。

或者,您可以让每个类实例化自己的记录器对象,而不需要传递任何内容。记录器可能需要“凭魔力”知道一些事情才能起作用,但是在类定义中对文件路径进行硬编码可能是与正确地将其正确传递给数百个不同的类相比,它具有更大的可维护性和乏味性,并且可以说,与使用全局变量绕过所述乏味相比,其邪恶性要小得多。(公认的是,记录器是全局变量的极少数合法用例之一)


1

我同意那些建议记录器应该被静态访问而不是传递给类的建议。但是如果你想要将它传递一个强有力的理由(也许是不同的情况下,要登录到不同的位置或东西),那么我会建议你不要使用构造函数传递,而是进行单独的调用来做到这一点,例如Class* C = new C(); C->SetLogger(logger);相当比Class* C = new C(logger);

选择此方法的原因是,记录器本质上不是类的一部分,而是用于其他目的的注入功能。将其放在构造函数列表中会使它成为类的要求,并暗示它是类的实际逻辑状态的一部分。例如,可以合理地预期(对于大多数类,虽然不是全部),但如果X != Y这样,C(X) != C(Y)但是如果您也比较同一类的实例,则不太可能测试记录器不平等。


1
当然,这样做的缺点是,记录器对构造函数不可用。
Ben Voigt 2015年

1
我真的很喜欢这个答案。它使日志记录成为您必须单独关心的类的侧面问题。很有可能,如果要将记录器添加到大量类的构造函数中,则可能正在使用依赖项注入。我不能说所有的语言,但是我知道在C#中,某些DI实现也将直接注入属性(getter / setter)。
jpmc26 2015年

@BenVoigt:是的,这可能是一个杀手级的原因,所以不这样做,但是通常情况下,您可以在构造函数中进行日志记录,以响应设置了记录程序的情况。
杰克·艾德利

0

值得一提的是,在这里我还没有看到其他答案,这是因为通过使记录器通过属性或静态方式注入,使得难以对类进行单元测试。例如,如果使记录器通过属性注入,则现在每次测试使用记录器的方法时都必须注入该记录器。这意味着您最好将其设置为构造函数依赖项,因为该类确实需要它。

静态也适用于同一问题;如果记录器不起作用,则整个类都会失败(如果您的班级使用记录器),即使记录器不一定是类责任的“一部分”,尽管它并没有像注入属性那样糟糕,因为至少知道记录器在某种意义上总是“存在”。

只是需要一些思考,特别是如果您使用TDD。我认为,记录器实际上不应该属于类的可测试部分(当您测试该类时,您也不应同时测试您的日志记录)。


1
嗯...所以您希望您的班级执行日志记录(日志记录应在规范中),但您不想使用记录器进行测试。这可能吗?我认为您的观点是不可行的。显然,如果您的测试工具损坏了,您将无法进行测试-以一种不依赖于测试工具的方式进行设计有点过度设计IMHO
Hogan

1
我的观点是,如果我使用构造函数并在类上调用方法,但由于未设置属性而仍然失败,则该类的设计者会误解了构造函数背后的概念。如果类需要记录器,则应将其插入构造函数中-这就是构造函数的用途。
Dan Pantry 2015年

嗯..不是真的。如果您将日志记录系统视为“框架”的一部分,那么将其作为构造函数的一部分是没有意义的。但这已在其他答案中明确指出。
霍根2015年

1
我反对财产注入。我不一定提倡在构造函数中使用它。我只是说,我认为这比注入财产更好。
Dan Pantry 2015年

“这可能吗?” 同样,是的,IL编织已经存在并且在最佳答案中提到 ... mono-project.com/docs/tools+libraries/libraries/Mono.Cecil
Dan Pantry

0

我懒得将记录器对象传递给每个类实例。因此,在我的代码中,这类事情要么位于静态字段中,要么位于静态字段中的线程局部变量。后者很酷,它允许您为每个线程使用不同的记录器,并允许您添加打开和关闭日志记录的方法,这些方法在多线程应用程序中可以实现有意义的工作。

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.