Proszę o ocenę czytelności kodu

Proszę o ocenę czytelności kodu
CyanApple
  • Rejestracja:ponad 13 lat
  • Ostatnio:około rok
  • Postów:23
0

Cześć,
Pracuję ostatnio nad poprawą czytelności mojego kodu, mam więc pytanie. Jak oceniacie czytelność kodu zamieszczonego poniżej

Kopiuj
	public static String swapEveryTwoLetters(final String wordIn) {
		if (wordIn != null) {
			if (wordIn.length() == 0) {
				return "Error: The word is epmty!";
			} else if (wordIn.length() == 1) {
				return wordIn;
			} else if (wordIn.length() >= 2) {
				char[] wordOut = wordIn.toCharArray();
				int firstLetterIndex = 0;
				int secondLetterIndex = firstLetterIndex+1;
				int lettersDistance=2;
				do {
					swapLetters(wordOut,firstLetterIndex,secondLetterIndex);
					firstLetterIndex += lettersDistance;
					secondLetterIndex = firstLetterIndex + 1;
				} while (secondLetterIndex < wordIn.length());
				if (wordLengthIsOdd(wordIn)) {
					wordOut[firstLetterIndex] = wordIn.charAt(firstLetterIndex);
				}
				return new String(wordOut);
			}
		} else {
			return "Error: The word is null!";
		}
		return "Why?"; //1
	}

Jeszcze jedno pytanie odnośnie kodu:
Dlaczego nie kompiluje się jeżeli usunę linię z komentarza 1?

hauleth
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:13 dni
0

Nie kompiluje się bo każda funkcja, która nie zwraca void musi zawsze coś zwrócić, a kompilator nie wie, że else musi się wykonać, jeśli warunek będzie niespełniony. Jeśli nie chcesz mieć tej dodatkowej linijki to napisz:

Kopiuj
public static String myFunc() {
  if (/* jakiś warunek/*) {
    // jakiś tam kod w którym jest return
  }
  return "Error: The word is null!";
}

Choć w tym przypadku powinieneś rypnąć wyjątkiem lub zwrócić null.


RE
autentycznie kompilator nie wykryje takiego prostego warunku? w innych językach można stosować takie konstrukcje. ba, w C# kompilator bada wszystkie możliwe ścieżki rozgałęzień, a nawet analizuje to, co w tych warunkach jest.
CyanApple
  • Rejestracja:ponad 13 lat
  • Ostatnio:około rok
  • Postów:23
0

Ok, zrozumiałem, dzięki :) A jak jest z czytelnością?

KR
  • Rejestracja:prawie 16 lat
  • Ostatnio:6 miesięcy
  • Postów:2514
0

jak na mnie brzydko sformatowany kod i za długie nazwy zmiennych :P


░█░█░█░█░█░█░█░█░█░█░█░
Zobacz pozostałe 4 komentarze
KR
dlatego mówię, że jak jest mało zmiennych. jak robi się więcej to lepiej dawać długie.
kogucik
Wszędzie gdzie zachodzi potrzeba umieszczenia komentarza przy zmiennej, należy ustawić znaczącą nazwę.
CyanApple
@Zjarek: a co jeżeli np. mamy tablicę pracowników, czy lepiej napisać wtedy np. for(int i=0;i<employees.length();++i) czy może for(int employeeNumber=0;employeeNumber<employees.length();++employeeNumber)?
Koziołek
for(Employee employee : employees) - chyba lepiej
CyanApple
tak, skasowałem taką wersję podczas edycji nie wiedzieć czemu :)
mychal
  • Rejestracja:ponad 15 lat
  • Ostatnio:ponad 8 lat
  • Lokalizacja:Przedmonitorze Górne
0

Ja bym to napisał bez else-ów(jeden poziom zagnieżdżenia mniej).


I fart u die.
ZJ
  • Rejestracja:około 14 lat
  • Ostatnio:około 12 lat
1

Ja bym zapisał to tak, wg mnie jest czytelniej:

Kopiuj
 
        public static String swapEveryTwoLetters(final String wordIn) {
                
		if (wordIn == null)
			return "Error: The word is null!";
		if (wordIn.length()==0)
			return "Error: The word is epmty!";
                if (wordIn.length() == 1) 
                        return wordIn;

                char[] wordOut = wordIn.toCharArray();
                int firstLetterIndex = 0;
                int secondLetterIndex = firstLetterIndex+1;
                int lettersDistance=2;

                do {
                        swapLetters(wordOut,firstLetterIndex,secondLetterIndex);
                        firstLetterIndex += lettersDistance;
                        secondLetterIndex = firstLetterIndex + 1;
                } while (secondLetterIndex < wordIn.length());

                if (wordLengthIsOdd(wordIn)) {
                        wordOut[firstLetterIndex] = wordIn.charAt(firstLetterIndex);
                }

                return new String(wordOut);
        }

Nie wiem czy błędy powinny być zwracane przez specjalne stringi, ja bym albo rzucił wyjątek, albo zwrócił nulla i ustawił zmienną globalną informującą o rodzaju błędu.

edit/ dodatkowo zmienna secondLetterIndex wydaje się zbędna.

edytowany 1x, ostatnio: Zjarek
hauleth
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:13 dni
0

Ujdzie, choć osobiście powywalał bym większość ifów i zwracał null jeśli argument jest nullem a "" jeśli string jest pusty.


Koziołek
Moderator
  • Rejestracja:około 18 lat
  • Ostatnio:19 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
1

Szczerze... kod jest do d**y. Za dużo ifów w jednej metodzie. Za dużo poziomów zagłębienia w jednej metodzie. Kod jest namieszany dlatego masz problem z kompilacją.

Po kawałku

Kopiuj
else if (wordIn.length() >= 2)

Brakuje jeszcze jednego else, które obstawiało by warunek wyjścia z metody jeżeli nie spełniony jest żaden z warunków.

Moja rada rozbij ten kod na mniejsze metody, które będą czytelniejsze będą miały jasną odpowiedzialność. Tu opchnąłeś wszystko w jednym miejscu przez co sam nie za bardzo ogarniasz przepływ sterowania w kodzie.

I żeby nie być gołosłownym:

Kopiuj
public static String swapEveryTwoLetters(final String wordIn) {
		if (wordIn != null && wordIn.length() > 0) {
			char[] wordOut = wordIn.toCharArray();
			swapWord(wordOut);
			return new String(wordOut);
		}
		return "Error: The word is null!";
	}

	private static void swapWord(final char[] wordOut) {
		int firstLetterIndex = 0;
		int secondLetterIndex = firstLetterIndex + 1;
		int lettersDistance = 2;
		while (secondLetterIndex < wordOut.length) {
			swapLetters(wordOut, firstLetterIndex, secondLetterIndex);
			firstLetterIndex += lettersDistance;
			secondLetterIndex = firstLetterIndex + 1;
		}
	}

	private static void swapLetters(char[] wordOut, int firstLetterIndex,
			int secondLetterIndex) {
		char a = wordOut[firstLetterIndex];
		char b = wordOut[secondLetterIndex];
		wordOut[firstLetterIndex] = b;
		wordOut[secondLetterIndex] = a;

	}

Chyba znacznie czytelniejszy kod...


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
adf88
Osobna funkcja do zamiany dwóch elementów tablicy to już lekka przesada. Ach Wy Javowcy, nawet dodawanie dwóch liczb byście funkcją zapisali...
Koziołek
nie funkcja, a metoda. Poza tym tworzenie niewielkich metod o bardzo ściśle określonej odpowiedzialności jest dobrą praktyką.
adf88
Oj tam, wiadomo, że metoda. Terminu "funkcja" używam jako ogólnik. W każdym razie przyznaję ci rację co do reguły, ale są pewne granice. Poza tym w jakim module umieścić by taką metodę? Moduł "tablice"? Problematyczne jest, gdy taka metoda ma "ogólne" przeznaczenie.
Koziołek
można by napisać rozszerzenie do Arrays, które by zawierało metodę reverse(Array, begin, end). Zrobiłbym tak jeżeli takie swapowanie potrzebne by było w więcej niż jednym miejscu.
CyanApple
  • Rejestracja:ponad 13 lat
  • Ostatnio:około rok
  • Postów:23
0
Zjarek napisał(a)

Ja bym zapisał to tak, wg mnie jest czytelniej:
Nie wiem czy błędy powinny być zwracane przez specjalne stringi, ja bym albo rzucił wyjątek, albo zwrócił nulla i ustawił zmienną globalną informującą o rodzaju błędu.
edit/ dodatkowo zmienna secondLetterIndex wydaje się zbędna.

Sposób zwracania błędów nie jest tutaj najważniejszy, dzęki jednak za radę. O ile nad rzuceniem wyjątku albo zwróceniem nulla bym sie jeszcze zastanowił to ustawianie zmiennej globalnej informującej o rodzaju błędu nie wydaje mi się za dobrym rozwiązaniem. W sumie chyba masz rację, że można usunąć. Początkowa wersja tego prostego algorytmu jej nie miała, jednak stwierdziłem, że zwiększy ona jego czytelność.

//edit:
@Koziołek: dzięki, biorę pod uwagę Twój komentarz, zastanowię się jeszcze nad tym.

edytowany 1x, ostatnio: CyanApple
Koziołek
Rzuć okiem na mojego bloga. Ostatnio piszę o zasadach tworzenia "kodu ekstremalnie obiektowego". Jedna z zasad brzmi "jeden poziom zagłębienia na metodę. Sprawdza się to fenomenalnie jeżeli chcesz uzyskać bardzo czytelny kod.
CyanApple
Racja, czytając ponownie mój kod nie mogę się nie zgodzić z tym, że za bardzo się zagłębiłem z ifami. Zaraz przeczytam bloga i po lekturze zrobię refaktoryzację na swój sposób :)
ZJ
Nie zawsze jest to dobre rozwiązanie, ale prawdą jest, że stosowanie więcej niż jednego poziomu zagłębienia jest dobrym powodem do zastanowienia się nad strukturą. Lepszym pomysłem jest nadawanie w miarę krótkich nazw metod, dobrze opisujących ich działanie. Jeżeli nie możesz metody dobrze nazwać, to najprawdopodobniej ta funkcja robi zbyt dużo, lub sam nie jesteś pewien co ona właściwie ma robić.
CyanApple
  • Rejestracja:ponad 13 lat
  • Ostatnio:około rok
  • Postów:23
0

Jak teraz?

Kopiuj
class SentenceTwo {
	public String swapEveryTwoLetters(final String wordIn) {
		if (wordIn != null && wordIn.length() > 0) {
			return swapWord(wordIn);
		}
		else {
			return "The word is null!";
		}
	}

	private String swapWord(final String wordIn) {
		char[] wordOut = wordIn.toCharArray();
		int firstLetterIndex = 0;
		int lettersDistance=2;
		while (firstLetterIndex + 1 < wordIn.length()) {
			swapLetters(wordOut,firstLetterIndex,firstLetterIndex+1);
			firstLetterIndex += lettersDistance;
		} 
		return new String(wordOut);		
	}
		
	private void swapLetters(char[] wordIn, int firstLetterIndex, int secondLetterIndex) {
		char tmpLetter = wordIn[firstLetterIndex];
		wordIn[firstLetterIndex] = wordIn[secondLetterIndex];
		wordIn[secondLetterIndex] = tmpLetter;
	}
}
edytowany 2x, ostatnio: CyanApple
Zobacz pozostały 1 komentarz
CyanApple
Dzięki, właśnie to wyedytowałem. Oczywiście, że nic nie robi, to zaszłość z wcześniejszej wersji kodu, gdzie w wordOut nie było całego wordIn.
Koziołek
bez else, bo tylko zaciemnia kod.
CyanApple
Bez else w swapEveryTwoLetters?
Koziołek
Tak, słowo else jest słowem zakazanym ;) Zazwyczaj oznacza, że metoda robi co najmniej jedną rzecz za dużo.
CyanApple
Ok, zapamiętane ;]
Kerai
  • Rejestracja:ponad 16 lat
  • Ostatnio:ponad 2 lata
  • Lokalizacja:London
2

ja bym
return "The word is null!";
zamienił na
return null;
albo
throw new NullPointerException("word is null")

... sam String "The word is null!" jest kompletnie do niczego nikomu nieprzydatny...
takie coś to się wyświetla użytkownikowi programu, a nie przekazuje jako wartość zwrotna.

Przeklinam wszystkich takich "programistów", co zamiast sensownie obsłużyć błąd robią taki cyrk typu "The world is null!".. i domyśl się człowieku, jak tu coś takiego obsłużyć...
Wyobraź sobie później gdzieś w kodzie coś takiego:

Kopiuj
String swapped = spawEveryTwoLetters(costam2);
if(swapped.equals("The word is null!"));
{
    //przerywamy task i wyswietlamy komunikat
}
else
{
    // ustawiamy stan na sukces i przekazujemy wynik dalej
}

true story... kiedyś podobnie musiałem, bo jakiś pacan postanowił, że wyjątki są "za wolne do sterowania przepływem programu" .. tak, na pewno porównywanie stringów jest szybsze, zjeb głupi.

Koziołek
wyjątki do sterowania flowem to zło. Znacznie lepszym sposobem jest jakiś wprowadzenie stanu, ale nie za pomoca String, a np. enumów.
Kerai
zwracanie nulla w tym wypadku by starczyło.. tak samo w tamtym moim
CyanApple
Ok, zapamiętałem, zwracam null. Dzięki.
CyanApple
  • Rejestracja:ponad 13 lat
  • Ostatnio:około rok
  • Postów:23
0

Mam nadzieję, że to już wersja ostateczna:

Kopiuj
class SentenceTwo {
        public String swapEveryTwoLetters(final String wordIn) {
                if (wordIn != null && wordIn.length() > 0) {
                        return swapWord(wordIn);
                }
                else {
                        return null;
                }
        }
 
        private String swapWord(final String wordIn) {
                char[] wordOut = wordIn.toCharArray();
                int firstLetterIndex = 0;
                int lettersDistance=2;
                while (firstLetterIndex + 1 < wordIn.length()) {
                        swapLetters(wordOut,firstLetterIndex,firstLetterIndex+1);
                        firstLetterIndex += lettersDistance;
                } 
                return new String(wordOut);                
        }
 
        private void swapLetters(char[] wordIn, int firstLetterIndex, int secondLetterIndex) {
                char tmpLetter = wordIn[firstLetterIndex];
                wordIn[firstLetterIndex] = wordIn[secondLetterIndex];
                wordIn[secondLetterIndex] = tmpLetter;
        }
}

Dzięki temu tematowi i krytyce dowiedziałem się kilku rzeczy :) Zastanawiam się jeszcze nad najlepszym możliwym sposobem zwracania błędów i przeglądając wypowiedzi w tym wątku wydaje mi się, że jest to null lub ewentualnie zdefiniowanie własnego typu wyliczeniowego. Wyjątki powinno się stosować jedynie w sytuacjach kiedy np. nie można odczytać danych z pliku (plik niedostępny, zablokowany przez inny proces itp.) ale nie w przypadku błędu dzielenia przez 0?

edytowany 1x, ostatnio: CyanApple
Koziołek
NIE UŻYWAJ SŁOWA ELSE W TYM PRZYPADKU. NIE JEST POTRZEBNE.
Koziołek
Moderator
  • Rejestracja:około 18 lat
  • Ostatnio:19 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
3

W sumie jeżeli zwracamy null to można napisać tak:

Kopiuj
public static String swapEveryTwoLetters(final String wordIn) {
                String out = null;
                if (wordIn != null) {
                        char[] wordOut = wordIn.toCharArray();
                        swapWord(wordOut);
                        out = new String(wordOut);
                }
                return out;
        }

w ten sposób masz jeden punkt wyjścia z metody niezależnie co robisz.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
CyanApple
  • Rejestracja:ponad 13 lat
  • Ostatnio:około rok
  • Postów:23
0

Ok, niech już tak zostanie :) Jestem po prostu po przeczytaniu Kod doskonały / Code Complete - http://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670 i próbuję stopniowo wcielać rzeczy tam opisane w życie.

edytowany 4x, ostatnio: CyanApple
LN
  • Rejestracja:około 16 lat
  • Ostatnio:około rok
  • Postów:1398
0

Przeczytaj jeszcze "Czysty kod", "Piękny kod" i "Java. Efektywne programowanie." ;)

CyanApple
Czy "Czysty kod" i "Piękny kod" nie powielają przypadkiem może tej samej wiedzy?
LN
Nie. "Piękny kod" jest bardziej ogólny. Wykracza też poza samo programowanie trochę.
CK
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 13 lat
0
Kopiuj
public static String swapPairs(String s) {
	if (s.length() <= 1) {
		return s;
	}
	else {
		return  "" + s.charAt(1) + s.charAt(0) + swapPairs(s.substring(2));
	}
}

Jeśli funkcja nie ma na celu przyjmowania gigantycznych danych (a tak podejrzewam w tym przypadku) to wersja rekurencyjna pod względem czytelności przebija imperatywne rozwiązania.

KR
w C++ może takie rozwiązanie by przeszło jeśli używałoby się char* i podawało w argumencie wskaźnik przesunięty o 2. Tutaj ten string będzie kopiowany więc złożoność kwadratowa zamiast liniowej. Na pewno jest czylniej. Do dłuższych stringów jak sam wspomniałeś kod za wolny.
CyanApple
  • Rejestracja:ponad 13 lat
  • Ostatnio:około rok
  • Postów:23
0

Nie pomyślałem w sumie o takim rozwiązaniu, ciekawe jak to by wyglądało pod względem wydajności... Trzeba będzie sprawdzić :)

CK
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 13 lat
0

Tutaj ten string będzie kopiowany więc złożoność kwadratowa zamiast liniowej.

Dzięki temu, że Stringi w Javie są niemodyfikowalne, metoda substring do tworzenia kolejnej instancji stosuje współdzielenie tej samej tablicy znaków. W tym celu używany jest niepubliczny konstruktor. Zatem kopiowanie nie jest tak straszne, jak piszesz.

Kopiuj
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
	this.value = value;
	this.offset = offset;
	this.count = count;
}
edytowany 2x, ostatnio: code_killer
Koziołek
Moderator
  • Rejestracja:około 18 lat
  • Ostatnio:19 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
0

@code_killer, nie do końca. O ile substring rzeczywiście nie będzie kosztowny, to już dodawanie stringów będzie bardzo kosztowne. Zauważ, że tworzysz od cholery obiektów, które mają bardzo krótki czas życia. To spowoduje, że JVM będzie poświęcał dużo czasu na tworzenie i zwalnianie obiektów, które jest pozbawione sensu.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
CyanApple
Czyli na stercie będzie bardzo duża fragmentacja pamięci i przez to odśmiecanie będzie bardzo kosztowne?
Koziołek
w J7 już mniej, bo G1 inaczej działa, ale w starszych może się tak zdarzyć.
CK
Bruce Eckel pisał w Thinking in Java, że GC odkłada obiekty na taśmę wyniku czego ciągłe stosowanie tworzenie/usuwanie Stringów nie musi być aż tak zabójczo kosztowne.
CK
  • Rejestracja:ponad 13 lat
  • Ostatnio:około 13 lat
0

Java efektywne programowanie[0] :-), tak znam to. W tym wątku postawiłem na czytelność, a nie wydajność. W zasadzie można robić dodatkowa podfunkcja składająca dane na podstawie StringBuildera, albo po prostu odejść od wersji rekurencyjnej. Tylko po co? Jeśli funkcja będzie stosowana rzadko albo dla niewielkiej długości danych to po co przejmować się kilkoma cyklami?

Gdy profiler wskaże, że funkcja jest za wolna wtedy można zmienić ją inną, a jeśli nie to cieszmy się prostotą w końcu czas programisty jest ważniejszy niż czas procesora. Z resztą rozmowy o wydajności na tle Javy są dla mnie bardzo satyryczne :-).

[0] - http://helion.pl/ksiazki/java-efektywne-programowanie-wydanie-ii-joshua-bloch,javep2.htm

edytowany 1x, ostatnio: code_killer
CyanApple
Chętnie bym ją przeczytał, ale patrząc na komentarze jakie są zawarte na stronie helionu myślę, że wybiorę wersję angielską jak tylko skończę czytać to co teraz czytam :) Dlaczego rozmowy o wydajności na tle Javy są dla Ciebie satyryczne?
Koziołek
Moderator
  • Rejestracja:około 18 lat
  • Ostatnio:19 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
1

Co do wydajności Javy, to różnie z tym bywa: http://en.wikipedia.org/wiki/Java_performance

Co zaś do Sposobu działanie GC w starszych wersjach javy, to jest on niezbyt wydajny. W przypadku Stringów sprawa dodatkowo się będzie komplikować, ponieważ masz w użyciu FlyWeight co oznacza, że tak naprawdę obiekty nie będą zawsze usuwane. Będzie pozostawała w pamięci niewielka ilość "śmiecia", który będzie czekał na nie wiadomo co.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
CK
Koziołek
Moderator
  • Rejestracja:około 18 lat
  • Ostatnio:19 dni
  • Lokalizacja:Stacktrace
  • Postów:6821
0

I dla zainteresowanych "debilotest" zużycia pamięci w przypadku tworzenia stringów inline:

Kopiuj
import java.io.FileNotFoundException;
import java.io.PrintStream;

public class StringMemTest {

	public static void main(String[] args) throws FileNotFoundException {
		System.out.println("Max memory: " + Runtime.getRuntime().maxMemory());
		System.out.println("Free memory at start: " + Runtime.getRuntime().freeMemory());
		System.setErr(new PrintStream("NIL"));// pod UXami dajemy: System.setErr(new PrintStream("/dev/null"));
		for (int i = 0; i < 1000000; i++) {
			System.err.println("" + (i - 1) + "" + (i + 1) + "" + i);
			logFreeMemory(i);
		}
	}

	private static void logFreeMemory(int i) {
		if (i % 10000 == 0) {
         		final StringBuilder stringBuilder = new StringBuilder("Free memory after ");
			System.out.println(stringBuilder.append(i).append(" : ")
					.append(Runtime.getRuntime().freeMemory()).toString());
		}
	}
}

Widać, ze GC śmiga co pewien czas, ale widać też, że nie powraca do pierwotnej ilość wolnej pamięci nawet w najlepszym okresie.


Sięgam tam, gdzie wzrok nie sięga… a tam NullPointerException
edytowany 1x, ostatnio: Koziołek
CyanApple
Sprawdziłem sobie dla 1000000 i strata wynosi w moim przypadku około 11MB.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.