2010-11-14 14 views
7

Mam pytanie dotyczące powielania kodu i refaktoryzacji, mam nadzieję, że nie jest zbyt ogólny. Załóżmy, że masz dość mały fragment kodu (~ 5 linii), który jest sekwencją wywołań funkcji , która jest - nie jest bardzo niskim poziomem. Ten kod powtarza się w kilku miejscach, więc dobrym pomysłem byłoby wyodrębnienie metody tutaj. Jednak w tym konkretnym przykładzie ta nowa funkcja ucierpiałaby z powodu niskiej spójności (co przejawia się, między innymi, trudnością w znalezieniu dobrego imienia dla funkcji). Powodem tego jest prawdopodobnie to, że ten powtarzający się kod jest tylko częścią większego algorytmu - i trudno jest go podzielić na dobrze nazwane kroki.Aby suszyć lub nie suszyć? O unikaniu duplikacji kodu i zachowaniu spójności

Co proponujesz w takim scenariuszu?

Edit:

Chciałem zachować pytanie na poziomie ogólnym, tak aby więcej osób mogło potencjalnie znaleźć przydatne, ale oczywiście najlepiej byłoby jego kopię zapasową z jakiejś próbki kodu. Przykładem może być jednym z najlepszych w historii (pachnie w dość kilka sposobów), ale mam nadzieję, że spełni swoje zadanie:

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

Odpowiedz

8

pytanie jest zbyt ogólne, aby naprawdę powiedzieć, ale jako ćwiczenie:

Załóżmy, że je zredukujesz. Zastanów się, jakie są prawdopodobne powody, dla których chcesz zmienić wynikową funkcję 5-liniową. Czy chcesz najprawdopodobniej wprowadzić zmiany dotyczące wszystkich użytkowników, czy też musiałbyś napisać nową funkcję, która różni się od starej, za każdym razem, gdy jakiś rozmówca ma powody, by chcieć zmiany?

Jeżeli chcesz zmienić go dla wszystkich użytkowników, jest to opłacalne abstrakcją. Daj mu teraz słabe imię, możesz później wymyślić coś lepszego.

Jeśli masz zamiar skończyć dzielenie tę funkcję w wielu podobnych wersjach jako kod ewoluuje w przyszłości, to chyba nie realną abstrakcją. Nadal można napisać funkcję, ale jest to raczej "oszczędzająca" funkcja "pomocnika" kodu, niż część formalnego modelu problemu. To nie jest zbyt satysfakcjonujące: powtórzenie tej ilości kodu jest nieco niepokojące, ponieważ sugeruje, że powinno być tam, które może być realną abstrakcją.

Może 4 z 5 linii mogą być wydobywane na zewnątrz, ponieważ są one bardziej spójna, a piąta linia właśnie tak dzieje się kręci z nimi w tej chwili. Wtedy możesz napisać 2 nowe funkcje: jedną, która jest tą nową abstrakcją, a druga jest tylko pomocnikiem, który wywołuje nową funkcję, a następnie wykonuje wiersz 5. Jedna z tych funkcji może mieć dłuższy oczekiwany okres użytkowania niż inny. .

+0

Próbuję bardzo ciężko znaleźć jakiś zwarty kawałek kodu, aby poprzeć moje pytanie, ale podstawa kodu, którą w pewnym sensie rzuciłam się na refaktor, wydaje się być takim bałaganem, że trudno jest wyizolować problem i prawdopodobnie trudno byłoby wkleić cokolwiek bez wchodzenia w długi i nudny opis. Myślę, że przybijałeś to z sugestią "tam gdzieś powinna istnieć realna abstrakcja gdzieś tam". To takie silne uczucie, że wciąż patrzę na kod, nie pozwalając mu odejść. Po prostu wiem, że istnieje lepszy sposób wyrażania całego pomysłu niż te spaghetti! – lukem00

+0

Dodałem kilka próbek kodu - czy chciałbyś rozszerzyć opis ogólnego podejścia z kilkoma konkretnymi wskazówkami i sugestiami - dla lepszej ilustracji? – lukem00

+0

@ lukem00: Nie wiem, może te 3 wspólne linie mogłyby zostać przekształcone w metodę 'SocketAction', zwaną również' onLoginCorrect'? Oznacza to, że każdy 'LoginHandler' powinien wykonać swoje zadanie, a następnie przekazać go do' SocketAction', która robi to z sesji. –

1

Myślałam o tym ostatnio i rozumiem dokładnie co ci chodzi. Wydaje mi się, że jest to abstrakcja implementacji bardziej niż cokolwiek innego, a zatem jest bardziej smaczna, jeśli można uniknąć zmiany interfejsu. Na przykład w C++ mogę wyodrębnić funkcję do cpp bez dotykania nagłówka. To nieco zmniejsza wymaganie, aby abstrakcja funkcji była dobrze sformułowana i znacząca dla użytkownika klasy, ponieważ jest dla nich niewidoczna, dopóki naprawdę jej nie potrzebuje (podczas dodawania do implementacji).

+0

Tak, to jest wewnętrzny materiał, więc interfejs pozostaje taki sam. Może to utopijny sposób myślenia, ale nadal uważam, że nawet prywatne metody powinny tworzyć przyzwoite abstrakcje. Powielanie kodu to jeden problem, a czytelność to kolejna.Chciałbym, aby wszystkie moje metody wyglądały jak metoda kompozytowa Becka * i były tak czytelne jak "Cunningham" z listopada (20, 2005) '... :) – lukem00

1

Dla mnie operatywne słowo to "próg". Innym słowem na to prawdopodobnie byłby "zapach".

Rzeczy są zawsze w równowadze. Brzmi jak (w tym przypadku), jak środek równowagi jest w spoistości (cool); a ponieważ masz tylko niewielką liczbę duplikatów, nie jest trudno zarządzać.

Jeśli pojawiło się jakieś większe "wydarzenie", a użytkownik przeszedł do duplikatu "1000", wówczas saldo ulegnie zmianie, a Ty możesz podejść.

Dla mnie kilka managable duplikatów nie jest sygnałem do refaktoryzacji (jeszcze); ale powinienem mieć na to oko.

+0

OK, to * jest * niezręczne, ponieważ ten temat jest podzielony na dwa pytania - przepraszam o tym. Domyślam się, że całe pytanie dotyczące kodu, który pokazuję, powinno być w rzeczywistości takie, jak "Jak można by wszystko zmienić, aby było bardziej czytelne?" - bo to jest prawdziwy problem, który tu mam. Obawiam się, że nikt go nawet nie przeczyta, więc próbowałem przeprogramować, więc wydaje się, że dotyczy to więcej osób niż tylko mnie. – lukem00

0

Dziedziczenie to Twój przyjaciel!

Nie duplikuj kodu. Nawet jeśli pojedyncza linia kodu jest bardzo długa lub trudna, należy ją przekształcić w osobną metodę o wyróżniającej nazwie. Pomyśl o tym jak o kimś, kto przeczyta Twój kod w ciągu roku. Jeśli nazwiesz tę funkcję "blabla", to czy następny facet będzie wiedział, co robi ta funkcja, nie odczytując jej kodu? Jeśli nie, musisz zmienić nazwę. Po tygodniu myślenia w ten sposób przyzwyczaisz się do tego, a Twój kod będzie bardziej czytelny niż Twój 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); 
     } 
    } 
} 
2

Dla mnie papierkiem lakmusowym jest: jeśli trzeba wprowadzić zmiany w tej sekwencji kodu w jednym miejscu (np dodać linię, zmienić kolejność), musiałbym zrobić ta sama zmiana w innych lokalizacjach?

Jeśli odpowiedź brzmi tak, to jest to logiczna, "atomowa" istota i powinna być refaktoryzowana. Jeśli odpowiedź brzmi "nie", to jest to sekwencja operacji, które działają w więcej niż jednej sytuacji - a jeśli zostaną refaktoryzowane, prawdopodobnie spowodują więcej problemów w przyszłości.