Kategorie
Junior Developer

Oceniam zadanie rekrutacyjne Junior Programista

W zeszłym tygodniu pisałem o tym, jak przejść perfekcyjnie rozmowę techniczną na Junior Programistę. W tym tygodniu pokażę Ci, na co zwracam uwagę podczas oceniania zadania rekrutacyjnego w czasie jej trwania. Zatem, zadanie rekrutacyjne Junior Programista, czas zacząć.

Lecimy!

Kontekst

Wróćmy na chwilę do procesu rekrutacyjnego. Być może zacząłeś go od zadania domowego, potem była rozmowa z HR, a teraz przyszedł czas na rozmowę techniczną (kolejność może być odmienna).

Podczas rozmowy technicznej sprawdzam umiejętności kandydata poprzez rozwiązanie krótkiego zadania z programowania.

Tak, jak pisałem we wcześniejszym artykule, podczas tej rozmowy na żywo stosuję dużo prostsze zadanie, niż te, które np. w czasie rekrutacji kandydat może rozwiązywać w domu. Zadanie jest prostsze, ale stanowi wyzwanie ze względu na to, że jest ono jednak na żywo i jest oceniane. I ma też ograniczony czas.

W kwestiach technicznych. Zainspirowany sposobem przeprowadzania rozmów online, które realizuje Clément Mihailescu, zacząłem też stosować Google Docs i czasem Jamboard w czasie rozmowy rekrutacyjnej. Generalnie Clément prowadzi kanał, który jest bardzo ciekawy i ma mnóstwo świetnych porad dla osób początkujących. Dlatego go polecam wszystkim, którzy chcą rozwinąć swoje umiejętności.

Pomysł na zadanie rekrutacyjne przyszedł mi do głowy również dzięki jego nagraniom. W jednym z nich problem do rozwiązania również zaczynał się od klawiatury telefonicznej, choć zadanie było o wiele bardziej skomplikowane. Nie zmienia to faktu, że klawiatura telefoniczna stała się głównym tematem mojego zadania rekrutacyjnego dla przyszłego junior programisty.

Problem do rozwiązania

Zadanie, które stosowałem podczas ostatniej rekrutacji wyglądało następująco:

Zapewne znasz klawiaturę w starych telefonach. Np. taką w Nokii 3310 lub Sony Ericsson K750i:

Zadanie rekrutacyjne online Java

W tych starych modelach, żeby napisać jakąś literę należało kilka razy wcisnąć dany przycisk. I tak np. żeby uzyskać A należało wcisnąć przycisk 2 jeden raz, a żeby uzyskać Z to przycisk 9 cztery razy.
Dlatego np. słowo KOT oznaczało wciśnięcie klawiszy 556668.

Twoim zadaniem jest napisanie metody, która dowolne słowo zamieni właśnie na taki ciąg cyfr.

Ciekawy jestem, czy Twoim zdaniem to zadanie rekrutacyjne jest proste, czy trudne na stanowisko Junior Programista. Tylko … weź pod uwagę, że jest ono robione na żywo, w Google Docs i z niejawnym ograniczeniem czasowym.

Nie muszę chyba dodawać, że kolejne rekrutacje, gdy będę miał okazję je prowadzić od strony technicznej, będą z zupełnie innym zadaniem 😊

Co oceniam w tym zadaniu?

Przede wszystkim zależy mi na poznaniu tego, w jaki sposób dana osoba dochodzi do rozwiązania. W tym procesie wg mnie najważniejsza jest umiejętność dekompozycji problemu.

W tym przykładzie może wydawać się to na wyrost. Ale nawet tutaj obserwator może zwrócić uwagę, że jednym z elementów rozwiązania może być poszukiwanie możliwości dalszego rozszerzenia modelu np. o inne klawiatury z innymi zestawami znaków.

Dlatego warto zwrócić uwagę, żeby już na początku nie zaczynać pisać linijek kodu, a poświęcić minutę, czy dwie, na spokojne zastanowienie się, jak podejść do zadania. A także zadać dodatkowe pytania odnośnie szczegółów.

Drugi element, jaki oceniam, to sposób opowiadania o kodzie, który właśnie się tworzy na żywo. Dzięki temu mogę stwierdzić, czy dany kandydat ułożył jakiś plan, czy raczej działa po omacku (i może się uda).

Zadanie rozwiązywane jest w Google Docs. Także nie zależy mi w tym miejscu na tym, żeby kod był w wersji „kompiluje się bez problemów”. W tym momencie oceniam ogólne podejście do kodowania. Mogę tu stwierdzić m. in. czy dana osoba już orientuje się w podstawach JDK, jakich konstrukcji używa, być może wzorców i czy samo pisanie kodu nie sprawia jej problemu.

Na końcu oczywiście istotne jest też to, czy rozwiązanie ma potencjał do poprawnego działania. Czyli w tym miejscu jest to dosyć zero-jedynkowa odpowiedź: tak, ten kod będzie działał po oszlifowaniu w IDE lub nie, nie będzie działał, nie ma szans.

Dekompozycja

Generalnie problem nie jest skomplikowany. Także nie należy się w nim doszukiwać jakichś ukrytych zawiłości.

Najłatwiej zacząć od zdekomponowania problemu na mniejsze fragmenty. Tutaj wydaje się, że tych małych bloczków będzie kilka:

  • Po pierwsze należy określić, jak ma wyglądać nasza metoda, czyli jej typy argumentów wejściowych i wyjściowych.
  • Następnie widzimy, że należy rozwiązać kwestię relacji między literami a przyciskami.
  • W dalszej kolejności warto zastanowić się, jak połączyć wszystkie dane, aby uzyskać wynik.

Wejście i wyjście

Na podstawie danych w zadaniu można bardzo szybko dojść do metody o takiej postaci:

public String encode(String text) {
   ...
}

Bardzo prosta. Przyjmuje jeden argument, nasz wejściowy text. Na wyjściu zwraca również wynikowy napis.

Co tutaj można zrobić lepiej?

Przede wszystkim warto zastanowić się, co by było, gdyby klawiatura była inna (inny alfabet, fragment alfabetu) lub gdybyśmy chcieli te układy klawiszy wymieniać. Wówczas elementem rozwiązania mogłoby być dodanie drugiego argumentu do metody, tak żeby uwzględniać taką możliwość. Argumentem tym mogłoby być właśnie mapowanie liter i przycisków.

Co może tu pójść nie tak?

Z moich obserwacji wynika, że w tym momencie najgorszą rzeczą, jak może się stać jest pominięcie tego elementu. Co to właściwie znaczy?

Chodzi o sytuację, gdy od razu rozpoczyna się pisanie kodu bez wydzielenia metody. Przede wszystkim prośba o napisanie metody jest w poleceniu. Jest to element zadania. Z drugiej strony od razu rozpoczęcie kodowania bez wydzielenia metody np. pętli for(…) w tym przykładzie prowadzi do rozwiązania niekompletnego, które nie jest podatne na dalszą rozbudowę.

Jak przechowywać relację liter i przycisków?

Sposobów jest kilka, ale … zwykle rozwiązanie najprostsze jest najlepsze. Ewidentnie narzuca się, że relacja między literami i ciągami cyfr jest rodzajem mapowania. Dlatego bardzo naturalnym rozwiązaniem jest tu wykorzystanie Map:

  private static final Map<String, String> mapper = new HashMap<>() {{
    put("A", "2");
    put("B", "22");
    put("C", "222");
    put("D", "3");
    ...
  }};

Rozwiązanie wydaje się proste. Z powyższej mapy od razu widzimy potrzebną relację: litera A to 2, B to 22, itd.

Co można tu zrobić lepiej?

W tym miejscu można pokusić się o próbę uogólnienia i wprowadzenie interfejsu, którego implementacje zwracałyby poszczególne ciągu cyfr dla danej litery. Jedna z takich implementacji mogłaby korzystać z przedstawionej powyżej mapy, inna być może z innych elementów.

W każdym razie w rekrutacji na stanowisko Junior Programista nie oczekuję takiego uogólnienia. Ale bardzo chętnie zobaczyłbym w tym miejscu wydzielenie dostępu do tej mapy poprzez jakąś metodę. Dlaczego? Żeby na przykład obsłużyć sytuację braku litery w naszej mapie i zwrócenie null.

Co może pójść tutaj jednak nie tak?

Na tym etapie najtrudniejszym elementem jest dobór struktury danych. Im prostsza tym lepsza. Dlatego utrudnienie sobie tego zadania poprzez specyficzną strukturę danych będzie prawdopodobnie prowadziło do licznych błędów w kodzie. Tak może być np. z tablicami dwuwymiarowymi, zbiorami (Set), listami par litera – przycisk oraz klasami reprezentującymi przyciski (choć to podejście może iść w stronę obiektowości). Innym podejściem, które niechybnie prowadzi do błędów, będzie próba wyliczania liczby cyfr dla danej litery na podstawie kodów ASCII.

Łączymy wszystko

Teraz kiedy mamy już dwa elementy nadszedł czas na połączenie ich w całość. Można to zrobić na wiele sposobów. Ja przygotowując to zadanie podzieliłem je sobie na trzy grupy rozwiązań:

  • podstawowe,
  • średniozaawansowane,
  • podejście z rekurencją.

Dlaczego tak?

Przede wszystkim chcę sprawdzić podstawowe umiejętności kodowania. Średniozaawansowane rozwiązanie będzie na pewno miłym zaskoczeniem. Tak samo, gdy ktoś wpadnie na rozwiązanie z rekurencją.

Podstawy są ważne

Rozwiązanie podstawowe. Używamy tutaj prostych konstrukcji, kod nie zawiera streamów i specjalnych fajerwerków. Zabezpieczam sobie przy okazji dostęp do mapy. Przykład poniżej:

  private String fromMapper(String letter) {
    return mapper.getOrDefault(letter, "");
  }

  public String encodeSimple(String word) {
    String result = "";
    if (Objects.nonNull(word)) {
      for (int i = 0; i < word.length(); i++) {
        String letter = String.valueOf(word.charAt(i));
        result += fromMapper(letter);
      }
    }
    return result;
  }

I tutaj moja dodatkowa uwaga. Na stanowisko Junior Programista tak rozwiązane zadanie rekrutacyjne podczas rozmowy online jest tym, które jest przeze mnie oczekiwanym. Bez komplikacji, podstawowe umiejętności kodowania przedstawione. Krótki czas rozwiązania, po prostu działa.

Co można tu pójść nie tak?

Przede wszystkim na tym etapie zdarzają się jakieś drobne pomyłki. Jedną z nich jest wpisanie indeksów na sztywno, np. text.charAt(0). To proste rzeczy, które w rozmowie o tym kodzie szybko można skorygować (dlatego tak ważne jest mówienie o swoim kodzie w trakcie pisania).

Inne rzeczy, na które rzadko zwracana jest uwaga to typy danych. Warto pamiętać, że char to nie to samo co String.

Jeszcze inny przykład to założenie, że w danych wejściowych nie będzie null. To nie jest duży problem, bo zadanie nie polega na sprawdzaniu null, ale prawie nikt na to nie zwraca uwagi podczas pisania (dlatego ja czasem pytam, a co by było gdyby …).

Rozwiązanie z wykorzystaniem reduce

Jeśli popatrzymy na ten problem z innej perspektywy to możemy zaobserwować, że łączenie danych możemy zrealizować nieco inaczej. Nasz wynik jest tak naprawdę połączeniem pustego łańcucha znaków z kolejnymi łańcuchami znaków dla każdej litery w naszym wyrazie będącym argumentem metody. Dlatego średniozaawansowane rozwiązanie mogłoby wyglądać następująco:

  public String encodeWithReduce(String word) {
    if (Objects.nonNull(word)) {
      String result = word.chars()
          .mapToObj(c -> String.valueOf((char) c))
          .reduce("", (accumulator, element) -> accumulator + fromMapper(element));
      return result;
    }
    return "";
  }

Co można tu pójść nie tak?

Nie miałem jeszcze takiego rozwiązania na stanowisko Junior Developer. Nie trudno tu jednak wskazać, że w tym rozwiązaniu trzeba już trochę więcej wiedzieć na temat strumieni, trzeba dobrze zmapować typ wchodzący do reduce i poprawnie przeprowadzić operację.

Rozwiązanie rekurencyjne

O to, czy kandydat widzi możliwość rozwiązania problemu rekurencyjnie staram się pytać prawie zawsze. Jestem ciekawy, czy wpadnie na takie rozwiązanie. Podobnie, jak przy reduce, w tym przypadku pomaga uzmysłownie sobie, że tak naprawdę przetwarzanie argumentu wejściowego jest przetwarzaniem głowy łańcucha znakowego i rekurencyjnie jego ogona. W ten sposób można dojść do takiego rozwiązania:

  public String encodeWithRecursion(String word) {
    if (Objects.nonNull(word) && word.length() > 0) { // sorry, nie wiem czemu tutaj higlighter uparł się na wstawienie encji HTML
      var mapped = fromMapper(String.valueOf(word.charAt(0)));
      return mapped + encodeWithRecursion(word.substring(1));
    }
    return "";
  }

Co można tu pójść nie tak?

Z dotychczasowych rekrutacji wiem, że rekurencja stanowi duży problem dla kandydatów. Wyodrębnienie odpowiednich fragmentów przetwarzania jest ważnym elementem, który tak naprawdę przychodzi z praktyką. Do tej pory nie miałem jeszcze przypadku, żeby ktoś rozwiązał to zadanie w ten sposób. Ale tak, jak i pisałem wcześniej, na stanowisko Junior Developer to rozwiązanie nie jest wymagane. Ale byłoby fajnym zaskoczeniem.

Tu przy okazji trzeba wiedzieć, jak zachowa się substring, żeby nie generować dodatkowych if’ów.

Podsumowanie

Mam nadzieję, że tym tekstem pomogę Tobie w lepszym przygotowaniu się do Twojej rozmowy kwalifikacyjnej na stanowisko Junior Programista.

Pewnie sam widzisz, że przykładowe zadanie nie jest nadmiernie trudne. Ale nawet w nim można znaleźć kilka ciekawych możliwości i miejsc, gdzie można wyraźnie pokazać swoje umiejętności.

Także daj znać, jeśli ten tekst jest dla Ciebie wartościowy. A tak z ciekawości, czy takie zadanie rekrutacyjne na stanowisko Junior Programista jest wg Ciebie łatwe czy trudne?

Jeśli masz jakieś pytanie, chcesz się czegoś dowiedzieć? Śmiało, wrzucaj komentarz. A może masz jakieś swoje przemyślenia na temat rekrutacji na Junior Programistę?

3.1 27 votes
Article Rating
Subscribe
Powiadom o
guest
9 komentarzy
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Robert
Robert
8 miesięcy temu

Witam,

dzięki za tekst – podziwiam ludzi, którzy mają czas, ochotę i (tak podejrzewam) czerpią radość z dzielenia się wiedzą. 

Tekst przeczytałem z dużym zainteresowaniem i ze smutkiem przyznaję przed samym sobą, że jestem jakże typowym juniorem – nie tylko podczas stresującej rozmowy rekrutacyjnej, ale i w zaciszu domowego biurka nie wpadłbym na rozwiązanie rekurencyjne… 

Ale ja nie w tym temacie. Zastanawia mnie, jak zaprezentowana metoda, zdekoduje na przykład napis: DEMON, który zakodowany będzie wyglądał tak:

333666666

3(D)33(E)6(M)66(O)666(N).

Pozdrawiam serdecznie, 

Robert

Robert
Robert
8 miesięcy temu

Całe życie powtarzam sobie – musisz być uważniejszy. Nie przeczytałem z uwagą treści zadania i założyłem, że proces powinien przebiegać w dwie strony 😉 (ech nigdy nie przestanę być pre-juniorem).

I jeszcze będę się teraz zamęczał, jak przeprowadzić takie dekodowanie – wydaje mi się, że nie jest to wcale takie trywialne.

Raf
Raf
8 miesięcy temu
Reply to  Robert

Moim zdaniem jest to niemożliwe, jeśli wejściowy ciąg nie jest podzielony na poszczególne litery 🙂
Ale swoją drogą ciekawym zadaniem byłoby wypisanie wszystkich możliwych słów dla danego ciągu cyfr. Słów poprawnych lub nie 😉

Grzesiek
Grzesiek
8 miesięcy temu
Reply to  Raf
Myślę, że można by się w przypadku dekodowania posłyżyć porównaniem otrzymanych ciągów liter z jakimś słownikiem językowym.
Dawid
Dawid
8 miesięcy temu

Super artykul i ogolnie z calym cyklem z nieba mi spadasz 🙂 obecnie szukam pracy jako junior developer, mialem przyjemnosc rozwiazywania zadan rekrutacyjnych, ale brakowalo mi wlasnie takich porad jak do tego podejsc.

Michal
Michal
8 miesięcy temu

Czy rekurencję traktujesz jako coś pozytywnego czy jako niepotrzebne komplikowanie prostego zadania?