2010-11-14 62 views
7

我有一個關於代碼重複和重構的問題,希望它不是太籠統。假設你有一段相當小的代碼(~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(); 
     } 
    } 
} 

回答

8

問題過於籠統說真的,但作爲一個練習:

假設你抽象它。想想可能的原因是想要改變最終的5線功能。您是否希望可能對所有用戶進行更改,或者您最終是否必須編寫一個與舊版本稍有不同的新函數,每次某個調用者有理由想要更改?

如果您想要爲所有用戶更改它,這是一個可行的抽象。現在給它一個可憐的名字,以後你可能會想到更好的名字。

如果您最終將此功能拆分爲許多與將來的代碼演變類似的版本,則可能不是可行的抽象。你仍然可以編寫這個函數,但它更像是一個節省代碼的「幫助函數」,而不是你正式的問題模型的一部分。這不太令人滿意:重複這段代碼有點令人擔憂,因爲它暗示應該是在某處的可行抽象。

也許5條線中的4條可以被抽象出來,因爲它們更有凝聚力,而第五條線恰好在此刻與他們一起懸掛。然後你可以編寫兩個新的函數:一個是新的抽象,另一個只是一個幫助器,它調用新函數,然後執行第5行。其中一個函數可能比其他函數有更長的預期使用壽命。

+0

我努力尋找一些簡潔的代碼來備份我的問題,但是我有點質疑自己要重構的代碼庫似乎很混亂,以至於很難將問題隔離開來可能很難粘貼任何東西,而不會進入漫長而無聊的描述。我認爲你用「在某處應該有一個可行的抽象」的建議來釘住它。這是一種強烈的感覺,我仍然一直盯着代碼,拒絕放棄。因爲我只是*知道*有比這個意大利麪條更好的表達整個想法的方式! – lukem00 2010-11-15 00:41:05

+0

我已經添加了一些代碼示例 - 您想用一些特定的提示和建議來增強對一般方法的描述 - 以獲得更好的插圖嗎? – lukem00 2010-11-17 20:56:22

+0

@ lukem00:我不知道,也許這3條公共線可以變成'SocketAction'的一個方法,也叫做'onLoginCorrect'?也就是說,每個'LoginHandler'都可以完成它的任務,然後委託給'SocketAction',它執行會話。 – 2010-11-17 22:05:23

1

我最近在想這件事,我完全理解你在做什麼。對我而言,這是一個實現抽象,而不是任何事情,因此如果你可以避免改變一個接口,它就更加可口。例如,在C++中,我可能在不觸及頭部的情況下將函數提取到cpp中。這有點減少了對函數抽象的要求,使其對於類用戶來說是良構和有意義的,因爲直到他們真正需要它(在添加到實現中時)它們是不可見的。

+0

是的,這是內部的東西,所以界面保持不變。也許這是烏托邦式的思維方式,但我仍然認爲即使是私人方法也應該形成體面的抽象。代碼重複是一個問題,可讀性是另一個問題。我希望我的所有方法看起來像貝克的*組合方法*,並且與坎寧安的「11月(2005年20)」一樣可讀......) – lukem00 2010-11-17 21:13:02

1

對我來說,操作詞是「閾值」。另一個詞可能是「嗅覺」。

事情總是處於平衡狀態。這聽起來像(在這種情況下)像平衡的中心在凝聚力(冷靜);由於您只有少量重複,因此不難管理。

如果你發生了一些主要的「事件」,並且你去了「1000」重複,那麼餘額將會改變,所以你可能會接近。

對我而言,一些可管理的副本不是重構的信號(尚未);但我會密切關注它。

+0

好吧,它*很難將這個主題分成兩個問題 - 對不起關於那個。我想我展示的這段代碼的整個問題實際上應該是這樣的:「你如何重構整個事物以使其更具可讀性?」 - 因爲這是我在這裏遇到的真正問題。我擔心沒有人會讀它,所以我試圖重新表述,這似乎與更多的人相關,而不僅僅是我。 – lukem00 2010-11-18 09:11:59

0

繼承是你的朋友!

請勿複製代碼。即使單行代碼很長或很難,也可以將其重構爲具有獨特名稱的單獨方法。想想它就像一個會在一年內閱讀你的代碼的人。如果你命名這個函數「blabla」,下一個人會知道這個函數在不讀取代碼的情況下做了什麼?如果不是,則需要更改名稱。經過一週的思考,你會習慣它,你的代碼將更具可讀性! ;)

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); 
     } 
    } 
} 
2

對我來說,試金石是:如果我需要做出改變的代碼,這個序列在一個地方(例如添加一行,更改順序),我需要做其他地點的變化相同嗎?

如果答案是肯定的,那麼它是一個邏輯的「原子」實體,應該重構。如果答案是否定的,那麼這是一系列操作發生在多種情況下 - 而且如果重構,將來可能會給您帶來更多麻煩。