12. Budujemy piaskownicę, czyli pomocnik programisty.

W dotychczasowych działaniach, dość wygodnie korzystało się nam z konsoli Eltena do tworzenia kodu. Gdybyśmy jednak chcieli pominąć etap konsoli i rozpocząć prace nad naszym własnym programem w jego dedykowanym folderze wewnątrz apps, natrafiamy na problemy:

  1. Każda zmiana w naszym programie wymaga przeładowania Eltena, co jest uciążliwe.
  2. Jeśli wprowadziliśmy przypadkiem jakiś błąd w kodzie, to dowiemy się o tym z okna błędu wyświetlanego przy starcie Eltena, albo będziemy musieli zaglądnąć do pliku elten.log.

Czy można jakoś zaradzić tym niedogodnościom?

Pomysł piaskownicy

Czy da się jakoś obejść konieczność restartu Eltena po każdej zmianie w programie? Rozwiązaniem mogłoby być polecenie przeładowania wszystkich programów, ale jeśli nasz program zawierać będzie jakiś błąd, to cały Elten i tak się wysypie.

Może więc napisać narzędzie, które:

  • Po uruchomieniu, załaduje nasz kod z pliku na dysku.
  • Jeśli wystąpi błąd wykonywania, wyświetli ten błąd i umożliwi ponowne uruchomienie naszego programu po skorygowaniu błędu.
  • Jeśli błąd nie wystąpi, to i tak po zakończeniu wykonywania da szybką możliwość ponownego uruchomienia.
  • Ponowne uruchomienie za każdym razem będzie poprzedzone wczytaniem aktualnej wersji pliku z dysku, więc łatwo będzie uruchamiać kolejne, nieco zmienione wersje.

Jak wczytać plik?

W Eltenie szybkie wczytanie pliku do zmiennej umożliwia funkcja readfile.

alert  readfile 'c:\windows\win.ini'

Przy okazji, chyba po raz pierwszy zrezygnowaliśmy z nawiasów okrągłych dla podawania parametrów funkcji i jak widać, wszystko i tak zadziałało prawidłowo. Ruby jest pod tym względem dość niewymagający, ale inne języki programowania, już niekoniecznie.

Jak wykonać własny kod Ruby?

W Ruby do wykonania własnego kodu, służy funkcja eval.

eval("a=2+2
alert(a)")

Jest to funkcja, z którą trzeba bardzo uważać. Gdybyśmy w jakimś systemie dali użytkownikowi możliwość wykonania własnego kodu, na przykład przez użycie eval, to taki użytkownik w danym systemie mógłby zrobić bardzo dużo, a właściwie to praktycznie wszystko.

Konsola Eltena daje nam taką możliwość i z wygody tego rozwiązania wielokrotnie korzystaliśmy i jeszcze skorzystamy. Gdyby jednak coś takiego było dozwolone na serwerze, dla każdego użytkownika, który odwiedzi jakąś stronę, byłoby niedobrze.

Są reguły i są wyjątki, czyli jak przechwycić błąd?

Skoro eval pobiera parametr w postaci zmiennej łańcuchowej, to znaczy, że może się tam znaleźć wszystko, np.:

eval (puts 4/0)

Wykonanie tego polecenia w konsoli wyświetliło nam błąd, ale jeśli chcielibyśmy pobrać zawartość tego komunikatu o niedozwolonym dzieleniu przez 0 do jakiejś naszej zmiennej łańcuchowej, aby następnie wyświetlić ją w oknie dialogowym Eltena, jak tego dokonać?

Wyjątki, czasem też wyjątki krytyczne, to rodzaj błędów, z którymi chyba każdy użytkownik się kiedyś spotkał w jakichś używanych przez siebie narzędziach. Są to zdarzenia, będące rezultatem jakichś nieoczekiwanych okoliczności (w naszym przypadku dzielenia przez 0). Programiści mogą przechwytywać wyjątki, czyli oprogramowywać co ma się wydarzyć w odpowiedzi na zaistnienie jakiegoś wyjątku.

Jeśli tego nie zrobią, nastąpi domyślna obsługa wyjątku, czyli zatrzymanie programu.

W Ruby obsługa wyjątku ma następującą konstrukcję:

begin
  a=0
  puts 4/a
rescue Exception => se
  puts ("Error occured #{se.message}")  
end

Kluczowa linia rescue Exception => se przypisuje obiekt wyjątku do zmiennej se, do której możemy się odwołać w bloku obsługi wyjątku.

Początek bloku begin jest zbędny, jeśli obsługą wyjątku chcielibyśmy objąć jakąś całą funkcję lub metodę tworzonej klasy.

Stałe nie takie stałe

W każdym języku programowania, prócz zmiennych, istnieją także stałe. Są to wartości, jak ich nazwa wskazuje, niezmienne w toku wykonywania programu. W różnych językach deklaruje się je przy pomocy słowa kluczowego const np.: const PI=3.1415.

W Rubym jest inaczej niż wszędzie i to bardzo inaczej:

  • Nie używamy słowa const, a o tym, że coś jest stałą, świadczy nazwa, która musi się zaczynać z dużej litery. Często stosowaną konwencją nazywniczą jest pisanie całej nazwy dużymi literami i rozdzielanie jej słuw podkreśleniami.
  • Wartości stałych, uwaga, mogą się zmieniać w trakcie wykonywania programu, co osobiście dla piszącego te słowa nie jest zrozumiałe ani racjonalnie wytłumaczalne, ale tak jest.
MY_CONST=123
MY_CONST=456
alert MY_CONST

Chyba we wszystkich prócz Rubiego językach, powyższy kod spowodowałby błąd w drugiej linii, w której próbujemy zmienić wartość stałej, pierwotnie zadeklarowanej w pierwszej linii. W Ruby takiego błędu nie ma.

Składamy piaskownicę w całość

Tworzymy w folderze Elten apps folder o nazwie app_dev_sandbox i w pliku __app.ini definiujemy typowy szkielet programu, znany z poprzednich odcinków.

=begin EltenAppInfo
Name=App Dev Sandbox
Version=1.0
Author=grzezlo
=end EltenAppInfo
class Program_AppDevSandbox < Program
  MY_PROGRAM_FILE_NAME = "myprogram.rb"

  def main
    menu = Menu.new
    menu.option("Run my prog") {
      run_my_prog
    }
    menu.option("Exit") {
      finish
    }
    menu.show
  end

Stała MY_PROGRAM_FILE_NAME to nazwa pliku z naszym kodem, tu myprogram.rb.

Kluczowa metoda, wywoływana po kliknięciu “Run my prog”, wygląda następująco:

def run_my_prog
    eval(readfile(appfile(MY_PROGRAM_FILE_NAME)))
  rescue Exception => se
    error_my_prog("Error #{se.message}")
  end

czyli jak to wyżej omówiliśmy: ewentualny wyjątek po eval jest przechwytywany do zmiennej se i wyświetlany w funkcji error_my_prog, która komunikat wyjątku prezentuje w formularzu Eltena z możliwością ponownego uruchomienia programu:

  def error_my_prog(errMsg)
    form = Form.new([
      edt_err = EditBox.new("Error", EditBox::Flags::MultiLine | EditBox::Flags::ReadOnly, errMsg, true),
      btn_retry = Button.new("Run again"),
      btn_cancel = Button.new("Cancel"),
    ], 0, false, true)
    btn_retry.on(:press) {
      run_my_prog
      form.resume
    }
    btn_cancel.on(:press) {
      form.resume
    }
    form.cancel_button = btn_cancel
    form.accept_button = btn_retry
    form.wait
  end
end

Po kliknięciu przycisku “Run again”, program jest ponownie wczytywany z pliku myprogram.rb i uruchamiany.

Jeśli zakończy się bez błędu, to wracamy do menu, gdzie poleceniem “Run my prog” możemy także wczytać i uruchomić go ponownie.

Testujemy myprogram.rb

To co zapiszemy w pliku myprogram.rb, zostanie wykonane, tak jakbyśmy uruchomili to z konsoli. Mając plik myprogram.rb otwarty w osobnym oknie w ulubionym edytorze plików tekstowych, możemy szybko się do niego przełączać, modyfikować, zapisywać i uruchamiać nowe wersje po powrocie do okna Eltena. Nie ryzykujemy także, jak to ma miejsce podczas eksperymentów w konsoli, że jakaś nieskończona pętla i konieczność zamykania Eltena doprowadzi nas do utraty eksperymentalnego kodu.

alert "This is just a simple test"

Po kliknięciu Run my prog, zadziałało zgodnie z oczekiwaniem, czyli tekst został wypowiedziany.

Sprowokujmy błąd

balert "This is just a simple test"

Otrzymujemy błąd: undefined method `balert’ z podaniem numeru wiersza w pliku, w którym wystąpił.

Szybko przełączamy się do okna z kodem, korygujemy błąd, zapisujemy i przyciskiem “Run again” uruchamiamy ponownie.

myprogram jako osobna aplikacja

Wygodnie byłoby prowadzić w tej piaskownicy prace nad osobną aplikacją Eltena.

  • Informacje nagłówkowe możemy umieścić na górze pliku, niczemu to nie przeszkadza, a oszczędzimy czasu później.
  • Klasa naszego programu, musi dziedziczyć z klasy Program_AppDevSandbox, abyśmy mieli dostęp do działającej funkcji appfile.
  • Na końcu pliku, po definicji klasy, musimy wyołać jej metodę main.

Mogłoby to więc wyglądać następująco:

=begin EltenAppInfo
Name=My great new app
Version=1.0
Author=grzezlo
=end EltenAppInfo
class Grzezlo_Eltendev_app02 < Program_AppDevSandbox
  def main
    alert("Appfile=#{appfile}")
  end #main
end #class

Grzezlo_Eltendev_app02.new.main

Wspólna przestrzeń nazw

Użycie jako nazwy klasy identyfikatora złożonego z nazwy użytkownika i dodatkowych słów ma gwarantować, że żadna inna aplikacja ani klasa Eltena, nie będzie się tak nazywać.

W rzeczywistości, użycie w moim poprzednim wpisie nazwy klasy SimpleGameSound, było dość ryzykowne, łatwo mogłoby się pokrywać z jakąś inną klasą w innej aplikacji.

Klasy używane w programie, można by umieścić również wewnątrz samej klasy programu, co łatwo rozwiązałoby ten problem, ale nie zrobiliśmy tego w odc. 8., aby nadmiernie nie utrudniać zrozumienia kodu.

Jeśli nawet wszystkie używane klasy umieścimy wewnątrz klasy programu, to klasa programu po prostu musi mieć unikalną nazwę we wspólnej przestrzeni nazw.

Ostatnia linia myprogram.rb, inicjalizuje obiekt klasy programu i wywołuje na nim metodę main, bez przypisywania do jakiejkolwiek zmiennej:

Grzezlo_Eltendev_app02.new.main

Pliki programu

Sprawdziliśmy, że poprzednie uruchomienie programu prawidłowo zaanonsowało zawartość ścieżki, w której został on umieszczony.

Jeśli do tego folderu skopiujemy jakiś dźwięk, np. plik good.ogg z odc. 8., to bez problemu możemy go odtwarzać:

  def main
    @so = Sound.new(appfile("good.ogg"))
    @so.play
  end #main

Wyjście z piaskownicy

W końcu doprowadzimy naszą aplikację do stanu, w którym jej uruchamianie w piaskownicy nie będzie dłużej potrzebne. Co trzeba wówczas zrobić, aby stała się ona samoistnym programem Eltena?

  1. Utworzyć osobny folder na program w folderze apps.
  2. Przenieść do tego folderu wszystkie pliki naszej aplikacji, tj. myprogram.rb i inne, np. dźwięki.
  3. Zmienić nazwę myprogram.rb na __app.rb.
  4. Zmienić definicję dziedziczenia klasy, aby dziedziczyła z klasy Program, czyli zamiast class App02 < Program_AppDevSandbox było class App02 < Program
  5. Usunąć ostatnią linię Grzezlo_Eltendev_app02.new.main.
  6. Zrestartować Eltena i sprawdzić, czy wszystko działa.

Gotowa piaskownica

Pobierz program app dev sandbox

6 komentarzy

  1. No proszę, nie przypuszczałem, że ktoś to jeszcze pamięta? Były czasy… W midletpascalu było to napisane, znalazłem kod źródłowy.
    Parę lat temu robiłem przymiarkę do przeportowania na JavaScript…

  2. Trzeba kiedyś Strefę XN przepisać na silniku Eltena żeby młodzi wiedzieli, co to było za dzieło. Niestety nie znam na pamięć układu wszystkich poziomów xD.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *