干还是不干?避免代码重复和保持凝聚力

时间:2010-11-14 15:26:05

标签: language-agnostic refactoring code-duplication

我有一个关于代码重复和重构的问题,希望它不是太笼统。假设您有一小段代码(~5行),这是一系列函数调用,它们不是非常低级别的。这个代码在几个地方重复,因此在这里提取方法可能是个好主意。然而,在这个特定的例子中,这个新的功能将遭受低内聚(其中,其中,通过难以找到该功能的良好名称)表现出来。原因可能是因为这个重复的代码只是更大算法的一部分 - 并且很难将其划分为命名良好的步骤。

在这种情况下你会建议什么?

编辑:

我希望将问题保持在一般水平,以便更多人可能发现它有用,但显然最好用一些代码示例来支持它。这个例子可能不是有史以来最好的(它在很多方面都有气味),但我希望它能够完成它的工作:

class SocketAction {

    private static class AlwaysCreateSessionLoginHandler extends LoginHandler {
        @Override
        protected void onLoginCorrect(SocketAction socketAction) throws IllegalAccessException, IOException {
            Server.checkAllowedDeviceCount(socketAction._sess.getDeviceID());
            socketAction.registerSession();
            socketAction._sess.runApplication();
        }
    }

    private static class AutoConnectAnyDeviceLoginHandler extends LoginHandler {
        @Override
        protected void onLoginCorrect(SocketAction socketAction) throws IllegalAccessException, IOException {
            if (Server.isUserRegistered(socketAction._sess.getUserLogin())) {
                Log.logSysInfo("Session autoconnect - acquiring list of action threads...");
                String[] sa = Server.getSessionList(socketAction._sess.getUserID());
                Log.logSysInfo("Session autoconnect - list of action threads acquired.");
                for (int i = 0; i < sa.length; i += 7) {
                    socketAction.abandonCommThreads();
                    Server.attachSocketToSession(sa[i + 1], socketAction._commSendThread.getSock());
                    return;
                }
            }
            Server.checkAllowedDeviceCount(socketAction._sess.getDeviceID());
            socketAction.registerSession();
            socketAction._sess.runApplication();
        }
    }

    private static class OnlyNewSessionLoginHandler extends LoginHandler {
        @Override
        protected void onLoginCorrect(SocketAction socketAction) throws IllegalAccessException, IOException {
            socketAction.killOldSessionsForUser();
            Server.checkAllowedDeviceCount(socketAction._sess.getDeviceID());
            socketAction.registerSession();
            socketAction._sess.runApplication();
        }
    }
}

5 个答案:

答案 0 :(得分:8)

问题太笼统,不能说,但作为练习:

假设你抽象它。想想想要改变最终的5行功能的可能原因是什么。您是否希望进行适用于所有用户的更改,或者您是否最终必须编写与旧版本稍有不同的新功能,每次某些调用者都有理由要进行更改?

如果您想为所有用户更改它,那么这是一个可行的抽象。现在给它一个不好的名字,你以后可能会想到一个更好的名字。

如果您最终将此功能拆分为许多类似版本,而您的代码将来会发展,那么它可能不是一个可行的抽象。您仍然可以编写该函数,但它更像是一个代码保存的“辅助函数”,而不是正式问题模型的一部分。这不是很令人满意:重复这段代码有点令人担忧,因为它表明应该在那里是一个可行的抽象。

也许5条线中的4条可以被抽象出来,因为它们更具凝聚力,而第五条线正好恰好与它们闲在一起。然后你可以编写两个新函数:一个是这个新的抽象,另一个是调用新函数然后执行第5行的帮助器。其中一个函数可能比另一个具有更长的预期使用寿命。

答案 1 :(得分:2)

对我来说,石蕊测试是:如果我需要在一个地方对这段代码进行更改,(例如添加一行,更改顺序),我是否需要进行相同的更改其他地方?

如果答案是肯定的,那么它是一个逻辑的“原子”实体,应该重构。如果答案是否定的,那么这是一系列恰好在多种情况下运作的操作 - 如果重构,将来可能会给您带来更多麻烦。

答案 2 :(得分:1)

我最近在考虑这个问题,而且我完全理解你的目标。在我看来,这是一个实现抽象而不是任何东西,因此如果你可以避免更改接口,那么它更适合。例如,在C ++中,我可能会在不触及标题的情况下将函数提取到cpp中。这有点减少了函数抽象对于类用户的良好形式和有意义的要求,因为它们在它们真正需要它之前是不可见的(当添加到实现时)。

答案 3 :(得分:1)

对我来说,操作词是“门槛”。另一个词可能是“闻”。

事情始终处于平衡状态。听起来像(在这种情况下)平衡的中心是凝聚力(酷);因为你只有少量重复,所以不难管理。

如果你发生了一些重大的“事件”并且你去了“1000”重复,那么余额就会变得很狡猾,所以你可能会接近。

对我来说,一些可管理的副本不是重构的信号(还);但是我会留意它。

答案 4 :(得分:0)

继承是你的朋友!

不要重复代码。即使单行代码很长或很困难,也要将其重构为具有独特名称的单独方法。把它想象成会在一年内阅读您的代码的人。如果你将这个函数命名为“blabla”,下一个人会不知道这个函数在没有读取代码的情况下做了什么?如果没有,您需要更改名称。经过一周的思考,你会习惯它,你的代码将 12%更具可读性! ;)

class SocketAction {

    private static abstract class CreateSessionLoginHandler extends LoginHandler {
        @Override
        protected void onLoginCorrect(SocketAction socketAction) throws IllegalAccessException, IOException {
            Server.checkAllowedDeviceCount(socketAction._sess.getDeviceID());
            socketAction.registerSession();
            socketAction._sess.runApplication();
        }
    }

    private static class AlwaysCreateSessionLoginHandler extends CreateSessionLoginHandler;

    private static class AutoConnectAnyDeviceLoginHandler extends CreateSessionLoginHandler {
        @Override
        protected void onLoginCorrect(SocketAction socketAction) throws IllegalAccessException, IOException {
            if (Server.isUserRegistered(socketAction._sess.getUserLogin())) {
                Log.logSysInfo("Session autoconnect - acquiring list of action threads...");
                String[] sa = Server.getSessionList(socketAction._sess.getUserID());
                Log.logSysInfo("Session autoconnect - list of action threads acquired.");
                for (int i = 0; i < sa.length; i += 7) {
                    socketAction.abandonCommThreads();
                    Server.attachSocketToSession(sa[i + 1], socketAction._commSendThread.getSock());
                    return;
                }
            }
            super.onLoginCorrect(socketAction);
        }
    }

    private static class OnlyNewSessionLoginHandler extends CreateSessionLoginHandler {
        @Override
        protected void onLoginCorrect(SocketAction socketAction) throws IllegalAccessException, IOException {
            socketAction.killOldSessionsForUser();
            super.onLoginCorrect(socketAction);
        }
    }
}