Już vim coś niecoś więcej o ruby.

Hej, jakby ktoś był zainteresowany tym, jak włączyć kolorowanie kodu w vim to tak:

:syntax on

Autoformatowanie wcięć kodu w Ruby wymaga większego zachodu. najpierw trzeba upewnić się, że mamy wtyczkę vim-ruby załadowaną w vim:

Polecenie:

:scriptnames

Wyświetla coś takiego:

  1: /home/coola/.vimrc                                                                                                                                                                                                 
  2: /usr/share/vim-7.3/share/vim/vim73/syntax/syntax.vim                                                                                                                                                               
  3: /usr/share/vim-7.3/share/vim/vim73/syntax/synload.vim                                                                                                                                                              
  4: /usr/share/vim-7.3/share/vim/vim73/syntax/syncolor.vim                                                                                                                                                             
  5: /usr/share/vim-7.3/share/vim/vim73/filetype.vim                                                                                                                                                                    
  6: /usr/share/vim-7.3/share/vim/vim73/indent.vim                                                                                                                                                                      
  7: /usr/share/vim-7.3/share/vim/vim73/ftplugin.vim                                                                                                                                                                    
  8: /usr/share/vim-7.3/share/vim/vim73/plugin/getscriptPlugin.vim                                                                                                                                                      
  9: /usr/share/vim-7.3/share/vim/vim73/plugin/gzip.vim                                                                                                                                                                 
 10: /usr/share/vim-7.3/share/vim/vim73/plugin/matchparen.vim                                                                                                                                                           
 11: /usr/share/vim-7.3/share/vim/vim73/plugin/netrwPlugin.vim                                                                                                                                                          
 12: /usr/share/vim-7.3/share/vim/vim73/plugin/rrhelper.vim                                                                                                                                                             
 13: /usr/share/vim-7.3/share/vim/vim73/plugin/spellfile.vim                                                                                                                                                            
 14: /usr/share/vim-7.3/share/vim/vim73/plugin/tarPlugin.vim                                                                                                                                                            
 15: /usr/share/vim-7.3/share/vim/vim73/plugin/tohtml.vim                                                                                                                                                               
 16: /usr/share/vim-7.3/share/vim/vim73/plugin/vimballPlugin.vim                                                                                                                                                        
 17: /usr/share/vim-7.3/share/vim/vim73/plugin/zipPlugin.vim                                                                                                                                                            
 18: /usr/share/vim-7.3/share/vim/vim73/syntax/ruby.vim                                                                                                                                                                 
 19: /usr/share/vim-7.3/share/vim/vim73/indent/ruby.vim                                                                                                                                                                 
 20: /usr/share/vim-7.3/share/vim/vim73/ftplugin/ruby.vim 


No to mam to. Tylko teraz trzeba wyedytować plik ~/.vimrc i upewnić się, że zawiera linie:

set nocompatible " We're running Vim, not Vi!
syntax on " Enable syntax highlighting
filetype on " Enable filetype detection
filetype indent on " Enable filetype-specific indenting
filetype plugin on " Enable filetype-specific plugins


No i teraz w Vim kombinacja klawiszy ggVG= co tłumaczy się na
  • gg - idż na początek pliku
  • V - włącz zaznaczanie
  • G - idź na koniec pliku
  • = - poformatuj wcięcia
Co daje dosyć zabawny efekt:

require "test-unit"

class FirstSmokeTestClass < Test::Unit::TestCase

        def test_if_this_test_framework_works

                assert_equal(2, 1 + 1)                                                                                                                                                                                  

        end

end

~                                                                                                                                                                                                                       
11 wierszy wciętych


Te wcięcia jak dla mnie są za duże. Wystarczą tylko dwie spacje. Jak to zmienić?

Internet mówi, że wystarczy dodać do pliku ~/.vimrc taką linię:

autocmd Filetype ruby setlocal ts=2 sts=2 sw=2

Po otwarciu pliku w vim od razu moim oczom ukazał się poprawnie wcięty kod. WOW:

require "test-unit"                                                                                                                                                                                                     
                                                                                                                                                                                                                        
class FirstSmokeTestClass < Test::Unit::TestCase                                                                                                                                                                        
                                                                                                                                                                                                                        
  def test_if_this_test_framework_works                                                                                                                                                                                 
                                                                                                                                                                                                                        
    assert_equal(2, 1 + 1)                                                                                                                                                                                              
                                                                                                                                                                                                                        
  end                                                                                                                                                                                                                   
                                                                                                                                                                                                                        
end

Oczywiście zmiana wcięć i operacja ggVG= działa tak samo dobrze :) 

Commit do tego TU

Ale, ale - podejrzałem kommit i okazało się, że w rzeczywistości w pliku są tabulatory. Czuję się oszukany :(


Ktoś tu sobie leci w kulki! Edytor sobie, a kod sobie. Nie wiem. Może Ty wiesz?

Tymczasem w projekcie... Na razie klient może sobie dodać swój numer telefonu do obiektu mediów. Chciałbym teraz, żeby każdy z klientów mógł zasubskrybować medium i w dalszej perspektywie medium mogło informować klientów o egzemplarzach swoich periodyków. Jak zawsze, gdy mamy fazę green procesu TDD piszemy test jednostkowy:

def test_if_media_have_subscibers_structure
 
    assert_not_nil @media.subscribers                                                                    
                                                                                                          
end

Od razu jestem na czerwono:

...E                                                                                                      
==========================================================================================================
Error: test_if_media_have_subscibers_structure(SubscriptionTests): NoMethodError: undefined method `subscr
ibers'

Wchodzę w fazę red procesu TDD i piszę kod produkcyjny. I tak w kółko. Test Driven Development jest dyscypliną. Nie musi być mądre to, co robię, ważne żeby przyniosło oczekiwany efekt. Porównując to do koksika ćwiczącego na siłowni - podnoszenie 1000 razy tego samego ciężaru, jako takie, jest głupie, nic nie wnoszące. Ale z punktu widzenia rozwoju mięśni - takie ciągłe, zdawałoby się nieefektywne działanie, w dłuższej perspektywie prowadzi do rozwoju bica i trica.

No więc, żeby ten test przechodził - napisałem następujący kod produkcyjny:

class Media                                                                                               
                                                                                                          
  attr_accessor :ads, :subscribers                                                                   
                                                                                                          
  def initialize                                                                                          
    @ads = Array.new                                                                                      
    @subscribers = Array.new                                                                       
  end                                                                                                     
                                                                                                          
  def add_advertising telephone_number                                                                    
    @ads.push telephone_number                                                                            
  end                                                                                                     
                                                                                                          
end 

Szybkie uruchomienie rake wskazuje, że jestem w fazie green.

Started                                                                                                   
....                                                                                                      
                                                                                                          
Finished in 0.000670749 seconds.                                                                          
4 tests, 8 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications---------------------
                                                                                                          
100% passed

Zielono, więc piszę test, który sprawdza czy mogę dodać aktualnego klienta do listy subskrybentów w klasie mediów.


def test_if_client_can_be_added_to_subscribers_list                                                      
                                                                                                          
    it_people = ItPeople.new                                                                              
                                                                                                          
    @media.add_to_subscibers it_people                                                                    
                                                                                                          
  end 

Czerwono i do kodu produkcyjnego:

def add_to_subscribers client                                                                           
    @subscribers.push client                                                                              
end

Dooobra i zielono, ale chciałbym mieć w tej tablicy subscribers tylko takie obiekty, które implementują interfejs Publicable zawierający metodę Publish.

A więc najpierw test. Gdy do metody add_to_subscribers wpadnie coś, co nie implementuje Publicable poproszę o wyjątek:

def test_if_there_an_exception_if_there_was_no_publicable_implementation_on_entry                       
                                                                                                          
    assert_raises do                                                                                      
      @media.add_to_subscribers 125                                                                       
    end                                                                                                   
                                                                                                          
  end

Blok assert_raises sprawdza, czy w danym bloku kodu - pomiędzy do i end poleciał wyjątek. Spodziewam się, że nie będzie można dodać liczby 125 do tej kolekcji.
I rzeczywiście poleciał wyjątek (inny), bo nie poleciał wyjątek (w kodzie produkcyjnym):

/usr/share/ruby-2.3.1/bin/ruby ./test/all_tests.rb
Loaded suite ./test/all_tests
Started
.....F
==========================================================================================================
Failure: <[]> exception expected but none was thrown.
test_if_there_an_exception_if_there_was_no_publicable_implementation_on_entry(SubscriptionTests)
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:37:in `test_if_there_an_exception_if_there_was
_no_publicable_implementation_on_entry'
     34: 
     35:   def test_if_there_an_exception_if_there_was_no_publicable_implementation_on_entry
     36: 
  => 37:                assert_raises do
     38:       @media.add_to_subscribers 125
     39:                end
     40:        end
==========================================================================================================
    

Jestem w fazie czerwonej, więc zawijam kiecę i lecę do kodu produkcyjnego:

def add_to_subscribers client                                                                           
  if client.instance_of? Publicable                                                                     
      @subscribers.push client                                                                            
  end                                                                                              
end

Metoda .instance_of? {nazwa_klasy} sprawdza, czy dany obiekt jest danego typu. Ok, nadal mam error - tym razem kompilacji, bo nie ma takiego bytu jak Publicable. Tworzymy odpowiedni interfejs i importujemy go.

Ale zaraz zaraz Ruby podobno nie ma w sobie interfejsów!

Jak to?

Okazuje się, że rzeczywiście nie ma interfejsów w Ruby, ponieważ Ruby jest językiem dynamicznie typowanym. W Ruby rozpoznajemy typ obiektu nie na podstawie deklaracji typu, ale poprzez badanie metod na tym obiekcie. To tak zwane kacze typowanie. Nazwa ta pochodzi od powiedzenia, że "Jeżeli coś chodzi jak kaczka i kwacze jak kaczka, to musi to być kaczką". Hmmm, ciekawe.



Ok, czyli moje sprawdzenie czy client jest obiektem pewnej klasy, zamieniam na sprawdzenie czy client ma konkretne metody. Dzieje się to przy użyciu:

.respond_to? :nazwa_metody

def add_to_subscribers client                                                                           
    if !client.respond_to? :publish                                                                       
      raise 'Argument should have publish method'                                       
    end                                                                                                   
                                                                                                          
    @subscribers.push client                                                                              
                                                                                                          
  end 

Czyli jeśli nie posiadasz metody publish to rzuć wyjątkiem.
Odpalam testy i okazuje się, że poprzedni test przestał działać:

Started                                                                                                   
..E                                                                                                       
==========================================================================================================
Error: test_if_client_can_be_added_to_subscribers_list(SubscriptionTests): RuntimeError: Argument should h
ave publish method                                                                                        
/home/coola/dsp2017/xp-simulator/src/Media.rb:17:in `add_to_subscribers'                                  
/home/coola/dsp2017/xp-simulator/test/subscription_tests.rb:31:in `test_if_client_can_be_added_to_subscrib
ers_list'                                                                                                 
     28:                                                                                                  
     29:   def test_if_client_can_be_added_to_subscribers_list                                            
     30:                                                                                                  
  => 31:                @media.add_to_subscribers @it_people                                              
     32:                                                                                                  
     33:        end                                                                                       
     34:     

A to dlatego, że rzeczywiście klasa ItPeople nie ma metody publish. Jeszcze nie ma ;)


class ItPeople                                                                                            
                                                                                                          
  attr_accessor :telephone_number                                                                         
                                                                                                          
  def publish                                                                                             
  end                                                                                                     
                                                                                                          
end 

No i jestem na zielono. Jestem tym zaskoczony, że w języku dynamicznym tak mało mam do czynienia z konkretnymi typami. Wydaje mi się, że taki język jest wygodniejszy do testowania jednostkowego. A już, że wyjątek rzuciłem przy użyciu słówka kluczowego raise i opisowego stringa to jest bajka :)  Ciekawe czy dynamiczne typowanie doprowadzi mnie do jakiejś katastrofy, czy nadal będę mógł jechać na autopilocie testów jednostkowych, mając zawiązane oczy na konkretne typy.

Kommit do tego TU.



Po publikacji tego posta koledzy i koleżanki na Slack-u odezwali się do mnie i podsunęli mi rozwiązanie problemu z tabulatorami. Dzięki wielkie! :)


Kommit z tego TU.

Brak komentarzy:

Prześlij komentarz