Do you want to pick up from where you left of?
Take me there

Debugging

Τα σφάλματα είναι αναπόσπαστο μέρος κάθε project, γιαυτό και χρειαζόμστε απασφαλμάτωση.

Σε αυτό το μάθημα θα μάθουμε για την απασφαλμάτωση κώδικα στην Elixir καθώς και για τα εργαλεία στατικής ανάλυσης που μας βοηθάνε να βρούμε πιθανά σφάλματα

IEx

Το πιο σαφές εργαλείο που έχουμε για την αποσφαλμάτωση του κώδικα της Elixir είναι το IEx.

Αλλά μην γελιέστε από την απλότητά του - μπορείτε να λύσετε τα περισσότερα προβλήματα της εφαρμογής σας με αυτό.

IEx σημαίνει Elixir's interactive shell.

Πιθανότατα έχετε ήδη συναντίσει το IEx σε ένα από τα προηγούμενα μαθήματα όπως το Βασικά όπου ο κώδικας Elixir εκτελείται διαδραστικά στο κέλυφος.

Η ιδέα εδώ είναι πολύ απλή.

Αποκτάτε πρόσβαση στο διαδραστικό κέλυφος στο γενικό πλαίσιο του κώδικα που θέλετε να κάνετε αποσφαλμάτωση.

Ας το δοκιμάσουμε.

Για να το κάνετε αυτό, δημιουργήστε ένα αρχείο με όνομα test.exs και μέσα σε αυτό γράψτε τα ακόλουθα:

defmodule TestMod do
  def sum([a, b]) do
    b = 0

    a + b
  end
end

IO.puts(TestMod.sum([34, 65]))

Και αν το τρέξετε - θα έχετε το προφανές αποτέλεσμα 34:

$ elixir test.exs
warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)
  test.exs:2

34

Αλλά τώρα ας περάσουμε στο συναρπαστικό κομμάτι - την αποσφαλμάτωση.

Γράψτε require IEx; IEx.pry στην γραμμή μετα το b = 0 και ας δοκιμάσουμε να το τρέξουμε ακόμη μια φορά.

Το αποτέλεσμα θα είναι κάπως έτσι:

$ elixir test.exs
warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)
  test.exs:2

Cannot pry #PID<0.92.0> at TestMod.sum/1 (test.exs:5). Is an IEx shell running?
34

Θα πρέπει να παρατηρήσετε αυτό το πολύ συμαντικό μήνυμα.

Όταν εκτελούμε μια εφαρμογή, ως συνήθως, το IEx μας προβάλει αυτό το μήνυμα αντί να μπλοκάρει την εκτέλεση του προγράμματος.

Για να εκτελεστεί σωστά θα πρέπει να περάσουμε στην έντολή μας το όρισμα iex -S.

Αυτό, τρέχει την εντολή mix μέσα στην εντολή iex έτσι ώστε η εκτέλεση της εφαρμογής να γίνει με έναν ειδικό τρόπο, τέτοιον ώστε να καλεί την IEx.pry για να σταματήσει την εκτέλεση της εφαρμογής.

Για παράδειγμα, iex -S mix phx.server για να κάνετε αποσφαλμάτωση στην Phoenix εφαρμογή σας. Στην περίπτωσή μας, η iex -r test.exs θα απαιτεί να έχει αυτό το αρχείο:

$ iex -r test.exs
Erlang/OTP 21 [erts-10.3.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)
  test.exs:2

Request to pry #PID<0.107.0> at TestMod.sum/1 (test.exs:5)

    3:     b = 0
    4:
    5:     require IEx; IEx.pry
    6:
    7:     a + b

Allow? [Yn]

Αφού δώσουμε την απάντηση y στην ερώτηση (Allow? [Yn]) ή πατώντας Enter, μπαίνουμε στην διαδραστική μορφή.

$ iex -r test.exs
Erlang/OTP 21 [erts-10.3.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

warning: variable "b" is unused (if the variable is not meant to be used, prefix it with an underscore)
  test.exs:2

Request to pry #PID<0.107.0> at TestMod.sum/1 (test.exs:5)

    3:     b = 0
    4:
    5:     require IEx; IEx.pry
    6:
    7:     a + b

Allow? [Yn] y
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
pry(1)> a
34
pry(2)> b
0
pry(3)> a + b
34
pry(4)> continue
34

Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution

Για να βγούμε από το περιβάλλον του IEx, είσαγουμε τους χαρακτήρες Ctrl+C δύο φορές, ή πληκτρολογούμε continue για να περάσουμε στο επόμενο σημείο διακοπής.

Όπως μπορείτε να δείτε, μπορείτε να εκτελέσετε κώδικα Elixir.

Παρ’όλα αυτά, ο περιορισμός είναι ότι δεν μπορούμε να αλλάξουμε μεταβλητές του υπάρχοντα κώδικα, εξ’ αιτίας της αμεταβλητότητας της γλώσσας.

Επίσης, μπορείτε να πάρετε τιμές για όλες τις μεταβλητές και να τρέξετε οποιονδήποτε υπολογισμό.

Σε αυτή την περίπτωση, το σφάλμα θα έιναι στο b που εκχωρείται εκ’ νέου στο 0, και η διεργασία sum σαν αποτέλεσμα έχει σφάλμα.

Σίγουρα, η γλώσσα έχει ήδη εντοπίσει αυτό το σφάλμα από την πρώτη εκτέλεση, αλλά αυτό είναι απλά ένα παράδειγμα!

IEx.Helpers

Ένα από τα πιο ενοχλητικά κομμάτια του να δουλεύεις με IEx είναι ότι δεν έχει ιστορικό εντολών που δώθηκαν σε περασμένες εκτελέσεις.

Για την επίλυση αυτού του προβλήματος, υπάρχει μια ξεχωριστή υποκατηγορία στο IEx documentation, όπου μπορείτε να βρείτε την λύση για την πλατφόρμα της επιλογής σας.

Μπορείτε επίσης να ψάξετε στην λίστα άλλων διαθέσημων βοηθημάτων στο IEx.Helpers documentation.

Dialyxir και Dialyzer

Το Dialyzer, ένας διαφορικός αναλυτής για προγράμματα Erlang είναι ένα εργαλείο για στατική ανάλυση κώδικα. Με άλλα λόγια διαβάζουν αλλά δεν τρέχουν κώδικα και τον αναλύουν π.χ. ψάχνοντας για κάποια σφάλματα, νεκρό, αχρείαστο ή απροσπέλαστο κώδικα.

Το Dialyxir είνα μια εργασία mix που απλοποιεί τη χρήση του Dialyzer στην Elixir.

Οι προδιαγραφές βοηθούν εργαλεία όπως το Dialyzer να καταλάβουν τον κώδικα καλύτερα. Αντίθετα με την τεκμηρίωση που είναι αναγνώσιμη και κατανοητή μόνο από άλλους ανθρώπους (αν υπάρχει και είναι καλογραμμένη), η spec χρησιμοποιεί πιο επίσημο συντακτικό και μπορεί να γίνει κατανοητή από μια μηχανή.

Ας προσθέσουμε το Dialyzer στο project μας. Ο πιο απλός τρόπος είναι να προσθέσουμε την εξάρτηση στο αρχείο mix.exs:

defp deps do
  [{:dialyxir, "~> 0.4", only: [:dev]}]
end

Τότε καλούμε:

$ mix deps.get
...
$ mix deps.compile

Η πρώτη εντολή θα κατεβάσει και εγκαταστήσει το Dialyxir. Μπορεί να σας ζητηθεί να εγκαταστήσετε το Hex μαζί του. Το δεύτερο συντάσσει την εφαρμογή Dialyxir. Αν θέλετε να εγκαταστήσετε το Dialyxir καθολικά, παρακαλώ διαβάστε την τεκμηρίωσή του.

Το τελευταίο βήμα είναι να τρέξετε το Dialyzer για να χτίσετε ξανά το PLT(Persistent Lookup Table - Πίνακας Μόνιμης Εύρεσης). Πρέπει να το κάνετε αυτό κάθε φορά που εγκαθιστάτε μια νέα έκδοση Erlang ή Elixir. Ευτυχώς, το Dialyzer δεν θα προσπαθήσει να αναλύσει τη βασική βιβλιοθήκη κάθε φορά που προσπαθείτε να το χρησιμοποιήσετε. Χρειάζονται μερικά λεπτά για να ολοκληρωθεί η λήψη.

$ mix dialyzer --plt
Starting PLT Core Build ...
this will take awhile
dialyzer --build_plt --output_plt /.dialyxir_core_18_1.3.2.plt --apps erts kernel stdlib crypto public_key -r /Elixir/lib/elixir/../eex/ebin /Elixir/lib/elixir/../elixir/ebin /Elixir/lib/elixir/../ex_unit/ebin /Elixir/lib/elixir/../iex/ebin /Elixir/lib/elixir/../logger/ebin /Elixir/lib/elixir/../mix/ebin
  Creating PLT /.dialyxir_core_18_1.3.2.plt ...
...
 done in 5m14.67s
done (warnings were emitted)

Στατική ανάλυση κώδικα

Τώρα είμαστε έτοιμοι να χρησιμοποιήσουμε το Dialyxir:

$ mix dialyzer
...
examples.ex:3: Invalid type specification for function 'Elixir.Examples':sum_times/1.
The success typing is (_) -> number()
...

Το μήνυμα από το Dialyzer είναι ξεκάθαρο: ο τύπος επιστροφής της συνάρτησής μας sum_times/1 είναι διαφορετικός από αυτόν που έχει οριστεί. Αυτό συμβαίνει γιατί η Enum.sum/1 επιστρέφει έναν αριθμό και όχι έναν ακέραιο αλλά ο τύπος επιστροφής της sum_times/1 είναι ακέραιος.

Από τη στιγμή που ο αριθμός δεν είναι το ίδιο πράγμα με τον ακέραιο δεχόμαστε ένα σφάλμα. Πως θα το διορθώσουμε; Πρέπει να χρησιμοποιήσουμε την συνάρτηση round/1 για να αλλάξουμε τον αριθμό σε έναν ακέραιο:

@spec sum_times(integer) :: integer
def sum_times(a) do
  [1, 2, 3]
  |> Enum.map(fn el -> el * a end)
  |> Enum.sum()
  |> round
end

Τελικά:

$ mix dialyzer
...
  Proceeding with analysis...
done in 0m0.95s
done (passed successfully)

Η χρήση των προδιαγραφών μαζί με εργαλεία ανάλυσης στατικού κώδικα μας βοηθάει να γράφουμε κώδικα που είναι αυτό-ελεγμένος και περιέχει λιγότερα σφάλματα.

Απασφαλμάτωση

Μερικές φορές η στατική ανάλυση του κώδικα δεν είναι αρκετή. Μπορεί να είναι αναγκαίο να καταλάβουμε τη ροή εκτέλεσης ώστε να βρούμε σφάλματα. Ο απλούστερος τρόπος είναι να βάλουμε εκφράσεις εξόδου στον κώδικά μας όπως η IO.puts/2 για να παρακολουθήσουμε τιμές και ροή κώδικα, αλλά αυτή η τεχνική είναι απαρχαιωμένη και έχει όρια. Ευτυχώς για εμάς, μπορούμε να χρησιμοποιήσουμε τον εντοπιστή σφαλμάτων της Erlang για να απασφαλματώσουμε τον κώδικά μας στην Elixir.

Ας δούμε μια βασική ενότητα:

defmodule Example do
  def cpu_burns(a, b, c) do
    x = a * 2
    y = b * 3
    z = c * 5

    x + y + z
  end
end

Και τώρα τρέξτε το iex:

iex -S mix

Και τώρα τρέξτε τον εντοπιστή σφαλμάτων:

iex > :debugger.start()
{:ok, #PID<0.307.0>}

Η ενότητα :debugger της Erlang παρέχει πρόσβαση στον εντοπιστή σφαλμάτων. Μπορούμε να χρησιμοποιήσουμε την start/1 για να τον παραμετροποιήσουμε:

Το επόμενο βήμα είναι να συνδέσουμε την ενότητά μας στον εντοπιστή σφαλμάτων:

iex > :int.ni(Example)
{:module, Example}

Η ενότητα :int είναι ένας ερμηνευτής που μας δίνει τη δυνατότητα να δημιουργούμε σημεία διακοπής και να μπούμε μέσα στην εκτέλεση του κώδικα.

Όταν ξεκινάτε τον εντοπιστή σφαλμάτων θα δείτε ένα νέο παράθυρο σαν αυτό:

Στιγμιότυπο οθόνης Εντοπιστή Σφαλμάτων 1

Αφού συνδέσουμε την ενότητά μας στον εντοπιστή σφαλμάτων θα είναι διαθέσιμη στο μενού στα αριστερά μας:

Στιγμιότυπο οθόνης Εντοπιστή Σφαλμάτων 2

Δημιουργία σημείων διακοπής

Ένα σημείο διακοπής είναι ένα σημείο στον κώδικα όπου η εκτέλεση μπορεί να διακοπεί. Έχουμε δύο τρόπους δημιουργίας σημείων διακοπής:

Ας προσπαθήσουμε να δημιουργήσουμε ένα σημείο διακοπής στο IEx:

iex > :int.break(Example, 8)
:ok

Αυτό ορίζει ένα σημείο διακοπής στη γραμμή 8 της ενότητας Example. Τώρα όταν καλέσουμε τη συνάρτησή μας:

iex > Example.cpu_burns(1, 1, 1)

Η εκτέλεση θα διακοπεί στο IEx και το παράθυρο εντοπισμού σφαλμάτων θα πρέπει να δείχνει κάπως έτσι:

Στιγμιότυπο οθόνης Εντοπιστή Σφαλμάτων 3

Ένα επιπρόσθετο παράθυρο με τον πηγαίο κώδικά μας θα εμφανιστεί:

Στιγμιότυπο οθόνης Εντοπιστή Σφαλμάτων 4

Σε αυτό το παράθυρο μπορούμε να δούμε την τιμή των μεταβλητών, να προχωρήσουμε στην επόμενη γραμμή ή να αξιολογήσουμε τις εκφράσεις. Η :int.disable_break/2 μπορεί να κληθεί ώστε να απενεργοποιήσουμε ένα σημείο διακοπής:

iex > :int.disable_break(Example, 8)
:ok

Για να ενεργοποιήσουμε ξανά ένα σημείο διακοπής μπορούμε να καλέσουμε την :int.enable_break/2 ή μπορούμε να διαγράψουμε ένα σημείο διακοπής με αυτό τον τρόπο:

iex > :int.delete_break(Example, 8)
:ok

Οι ίδιες λειτουργίες είναι διαθέσιμες στο παράθυρο του εντοπιστή σφαλμάτων. Στο κεντρικό μενού, Break, μπορούμε να επιλέξουμε Line Break και να ορίσουμε σημεία διακοπής. Αν επιλέξουμε μια γραμμή που δεν έχει κώδικα τότε το σημείο διακοπής θα αγνοηθεί, αλλά θα εμφανιστεί στο παράθυρο του εντοπιστή σφαλμάτων. Υπάρχουν τρείς τύποι σημείων διακοπής:

Αυτό ήταν όλο! Καλή απασφαλμάτωση!

Έπιασες λάθος ή θέλεις να συνεισφέρεις στο μάθημα; Επεξεργαστείτε αυτό το μάθημα στο GitHub!