Niestandardowe bloki: przewodnik po stylu

Przez lata zespół Blockly i Blockly Games zebrał wiele wniosków, które przydają się w opracowaniu nowych bloków. Poniżej znajduje się lista błędów, które popełniliśmy, lub błędy często popełniane przez innych użytkowników.

Są to ogólne wnioski, których nauczyliśmy się, używając stylu wizualnego aplikacji Blockly. Mogą one nie mieć zastosowania we wszystkich przypadkach użycia i projektach. Istnieją też inne rozwiązania. Nie jest to wyczerpująca lista problemów, jakie mogą napotkać użytkownicy, i sposobów ich zapobiegania. Każdy przypadek jest inny i wiąże się z pewnymi kompromisami.

1. Wersje warunkowe a pętle

W przypadku nowych użytkowników najtrudniejsze są bloki warunkowe i pętle. W wielu środowiskach opartych na blokach oba bloki należą do tej samej kategorii elementów sterujących, przy czym oba bloki mają ten sam kształt i ten sam kolor. Często prowadzi to do frustracji, ponieważ nowi użytkownicy mylą te dwie bloki. Blockly zaleca przeniesienie warunków i pętli do osobnych kategorii „Logic” i „Pętle”, każda w innym kolorze. Wskazuje to, że są to różne koncepcje, które zachowują się inaczej, mimo podobnych kształtów.

Zalecenie: rozdziel elementy warunkowe i pętle.

2. Listy oparte na jedynkach

Początkujący programiści źle reagują, gdy po raz pierwszy napotkają listy w modelu zerowym. W rezultacie Blockly podąża za Luą i Lambda Moo, tworząc listy i indeksowanie ciągów znaków w modelu jednokrotnym.

W przypadku bardziej zaawansowanych zastosowań Blockly obsługiwane są listy od zera, które ułatwiają przejście na tekst. Młodszym i bardziej zaawansowanym odbiorcom nadal zalecamy indeksowanie w pierwszej kolejności.

Rekomendacja: 1 to pierwsza liczba.

3. Dane wejściowe użytkownika

Parametr można uzyskać od użytkownika na 3 sposoby. Menu jest najbardziej restrykcyjne i dobrze sprawdza się w przypadku prostych samouczków i ćwiczeń. Pole do wprowadzania danych zapewnia większą swobodę i jest odpowiednie w przypadku bardziej twórczych działań. W przypadku bloku wartości (zwykle z blokiem cienia) istnieje możliwość obliczenia wartości (np. generatora losowego), a nie tylko wartości statycznej.

Zalecenie: wybierz metodę wprowadzania dla swoich użytkowników.

4. Obrazy blokowania na żywo

Dokumentacja bloków powinna zawierać obrazy, do których się odnoszą. Robienie zrzutów ekranu jest proste. Jeśli jednak jest 50 takich obrazów i aplikacja została przetłumaczona na 50 języków, otrzymujemy 2500 obrazów statycznych. Następnie schemat kolorów się zmienia i 2500 zdjęć trzeba ponownie zaktualizować.

Aby uwolnić się od tego koszmaru związanego z konserwacją, Blockly Games zastąpiło wszystkie zrzuty ekranu instancjami Blockly uruchomionymi w trybie „tylko do odczytu”. Wynik wygląda identycznie jak obraz, ale z pewnością jest aktualny. Tryb „tylko do odczytu” umożliwił internacjonalizację.

Zalecenie: jeśli obsługujesz więcej niż 1 język, użyj trybu „tylko do odczytu”.

5. Drugie lewe

Opinie dzieci w Stanach Zjednoczonych (chociaż niekoniecznie z innych krajów) pokazały gwałtowne zamieszanie. Problem ten został rozwiązany za pomocą strzałek. Jeśli kierunek jest względny (np. względem awatara), styl strzałki ma znaczenie. Strzałka prosta lub strzałka ↱ jest myląca, gdy awatar jest skierowany w przeciwną stronę. Najbardziej przydatna jest strzałka ⟳ – nawet wtedy, gdy kąt kąta jest mniejszy niż strzałka.

Zalecenie: w miarę możliwości uzupełnij tekst ikonami Unicode.

6. Bloki wysokiego poziomu

Gdy tylko jest to możliwe, należy stosować podejście wyższe, nawet jeśli zmniejsza to wydajność lub elastyczność działania. Przeanalizuj to wyrażenie Apps Script:

SpreadsheetApp.getActiveSheet().getDataRange().getValues()

W przypadku mapowania 1:1, które zachowuje wszystkie potencjalne możliwości, powyższe wyrażenie zostałoby utworzone z wykorzystaniem 4 bloków. Blockly ma jednak wyższy poziom i zapewnia jeden blok, który obejmuje całe wyrażenie. Celem jest optymalizacja pod kątem 95% przypadku, nawet jeśli utrudnia to pozostałe 5%. Blockly nie zastępuje języków opartych na tekście. Ma na celu pomoc użytkownikom w pokonaniu początkowej krzywej nauki i posługiwaniu się językami opartymi na tekście.

Zalecenie: nie konwertuj całego interfejsu API na ślepo na bloki.

7. Opcjonalne wartości zwracane

Wiele funkcji w programowaniu tekstowym wykonuje działanie, a potem zwraca wartość. Tej zwracanej wartości nie można użyć. Przykładem może być funkcja pop() stosu. Można wywołać metodę pop w celu pobrania i usunięcia ostatniego elementu. Można też wywołać ją w celu usunięcia ostatniego elementu, przy czym zwracana wartość jest ignorowane.

var last = stack.pop();  // Get and remove last element.
stack.pop();  // Just remove last element.

Języki oparte na blokach zwykle nie radzą sobie z ignorowaniem zwracanej wartości. Blok wartości musi być podłączony do elementu, który akceptuje wartość. Jest kilka strategii, które można wykorzystać w tej sytuacji.

a) Objaśnij problem. Większość języków blokowych projektuje język, aby tego uniknąć. Na przykład w Scratchu nie ma żadnych bloków mających zarówno efekty uboczne, jak i zwracaną wartość.

b) Utwórz dwie bloki. Jeśli ilość miejsca w zestawie narzędzi nie stanowi problemu, prostym rozwiązaniem jest zapewnienie po 2 bloków tego typu – 1 z wartością zwracaną i 1 bez wartości zwracanej. Wadą jest to, że może to prowadzić do skomplikowanego zestawu narzędzi z wieloma niemal identycznymi blokami.

c) Zmień jeden blok. Użyj menu, pola wyboru lub innego elementu sterującego, aby użytkownik mógł określić, czy w przypadku pojawienia się wartości zwracanej czy nie. Bryła zmienia kształt w zależności od dostępnych opcji. Przykładem może być blokada dostępu do listy w bloku Blockly.

d) Zjedz wartość. W pierwszej wersji aplikacji App Inventor stworzono specjalny blok potoku, który gromadził dowolną powiązaną wartość. Użytkownicy nie rozumieli tej koncepcji, a w drugiej wersji narzędzia App Inventor usunięta została pionowa kreska i zamiast tego użytkownicy zalecili po prostu przypisanie wartości do zmiennej niestandardowej.

Rekomendacja: każda strategia ma swoje wady i zalety. Wybierz rozwiązanie odpowiednie dla swoich użytkowników.

8. Rosnące bloki

Niektóre bloki mogą wymagać zmiennej liczby danych wejściowych. Przykładami mogą być bloki dodawania, które sumują dowolny zbiór liczb, blok if/elseif/else z dowolnym zestawem klauzul allow lub konstruktor list z dowolną liczbą zainicjowanych elementów. Jest kilka strategii, a każda z nich ma swoje wady i zalety.

a) Najprostszym sposobem jest złożenie przez użytkownika bloków składających się z mniejszych bloków. Przykładem może być dodanie 3 liczb przez zagnieżdżenie dwóch bloków dodawania o dwóch numerach. Innym przykładem jest dodanie tylko bloków „if/else” i zagnieżdżenie ich przez użytkownika w celu utworzenia warunków otherif.

Zaletą tego podejścia jest jego początkowa prostota (zarówno dla użytkownika, jak i dla programistów). Wadą jest to, że w przypadku dużej liczby zagnieżdżeń kod staje się bardzo niewygodny i trudny do odczytu i obsługi.

b) Możesz też dynamicznie rozwijać blok, tak by na końcu zawsze znajdowała się 1 bezpłatna wartość wejściowa. I podobnie, jeśli na końcu znajdują się 2 wolne dane wejściowe, blok usuwa też ostatnie dane wejściowe. To właśnie zastosować w pierwszej wersji programu App Inventor.

Blokady, które rosły automatycznie, nie zostały odrzucone przez użytkowników usługi App Inventor z kilku powodów. Po pierwsze, udział zawsze był bezpłatny, a program nigdy nie był „kompletny”. Po drugie, wstawianie elementu pośrodku stosu było frustrujące, ponieważ wiązało się z odłączeniem wszystkich elementów poniżej zmiany i ponownym ich połączeniem. Jest to jednak bardzo wygodne rozwiązanie, jeśli kolejność nie ma znaczenia, a użytkownicy nie muszą się niczym martwić o luki w programie.

c) Aby rozwiązać problem, niektórzy programiści dodają przyciski +/- do bloków, które ręcznie dodają lub usuwają dane wejściowe. Otwórz Roberta za pomocą dwóch takich przycisków, aby dodać lub usunąć dane wejściowe na dole ekranu. Inni deweloperzy dodają w każdym wierszu po 2 przyciski, aby umożliwić wprowadzanie i usuwanie elementów ze środka stosu. Inne dodają po dwa przyciski w górę i w dół w każdym wierszu, aby umożliwić zmianę kolejności stosu.

W tej strategii dostępne są różne opcje – od 2 przycisków na blok po 4 przyciski w wierszu. Z jednej strony istnieje ryzyko, że użytkownicy nie będą w stanie wykonać potrzebnych działań, a z drugiej – interfejs jest tak wypełniony przyciskami, że wygląda jak most statku kosmicznego Enterprise.

d) Najbardziej elastycznym podejściem jest dodanie do bloku dymka mutatora. Przycisk ten ma postać pojedynczego przycisku, który otwiera okno konfiguracji tego bloku. Elementy można dodawać, usuwać i zmieniać ich kolejność w dowolnym momencie.

Wadą tego podejścia jest to, że mutatory nie są intuicyjne dla początkujących użytkowników. Wprowadzanie mutatorów wymaga pewnej formy instrukcji. Aplikacje oparte na blokach, które są skierowane do młodszych dzieci, nie powinny używać mutatorów. Choć raz dowiedzieliśmy się, że są one nieocenione dla doświadczonych użytkowników.

Rekomendacja: każda strategia ma swoje wady i zalety. Wybierz rozwiązanie odpowiednie dla swoich użytkowników.

9. Generowanie czystego kodu

Użytkownicy zaawansowane Blockly powinni mieć możliwość patrzenia na wygenerowany kod (JavaScript, Python, PHP, Lua, Dart itp.) i od razu rozpoznać, jaki program został napisany. Oznacza to, że należy dołożyć wszelkich starań, aby zapewnić czytelność tego wygenerowanego maszynowo kodu. Nadmierne nawiasy, zmienne liczbowe, usunięte odstępy i szczegółowe szablony kodu utrudniają tworzenie eleganckiego kodu. Wygenerowany kod powinien zawierać komentarze i być zgodny ze wskazówkami dotyczącymi stylu w Google.

Rekomendacja – bądź dumny z wygenerowanego kodu. Pokaż je użytkownikowi.

10. Zależność od języka

Efektem ubocznym potrzeby czystego kodu jest to, że zachowanie Blockly jest w dużej mierze zdefiniowane jako zachowanie skompilowanego języka. Najpowszechniejszym językiem wyjściowym jest JavaScript, ale jeśli Blockly ma przeprowadzić kompilację krzyżową na inny język, nie należy podejmować bezzasadnych prób zachowania takiego samego zachowania w obu językach. Na przykład w języku JavaScript pusty ciąg to fałsz, a w Lua – prawda. Zdefiniowanie jednego wzorca działania kodu Blockly, który będzie wykonywany niezależnie od języka docelowego, spowodowałoby niemożliwy do utrzymania kod, który wygląda, jakby pochodził z kompilatora GWT.

Zalecenie: Blockly nie jest językiem. Pozwól, aby istniejący język wpływał na działanie.