تنقيح تطبيقات ريلز

من موسوعة حسوب

يقدّم هذا الدليل التقنيات اللازمة لتنقيح تطبيقات ريلز. بعد قراءة هذا الدليل، ستتعلّم:

  • الغرض من التنقيح.
  • كيفة تتبّع العلل والأخطاء التي لا تتعرّف عليها الاختبارات في تطبيقك.
  • طرقًا مختلفة للتنقيح.
  • كيفيّة تحليل أثر المكدس.

مساعدي العرض للتنقيح

احدى المهام الشائعة في التنقيح هي فحص محتويات متغيّر معيّن، لذا يوفر ريلز ثلاثة طرائق لفعل ذلك:

  • Debug
  • To_yaml
  • Inspect

debug

سيعيد المساعد debug الوسم <pre> الذي يصدّر الكائن باستخدام تنسيق YAML، وسيؤدي هذا إلى توليد بيانات يمكن للإنسان قراءتها من أي كائن. فعلى سبيل المثال، إذا كان لديك هذه الشيفرة البرمجيّة في العرض:

<%= debug @article %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

فسترى شيئًا مشابهًا لما يلي:

--- !ruby/object Article
attributes:
  updated_at: 2008-09-05 22:55:47
  body: It's a very helpful guide for debugging your Rails app.
  title: Rails debugging guide
  published: t
  id: "1"
  created_at: 2008-09-05 22:55:47
attributes_cache: {}
 
 
Title: Rails debugging guide

to_yaml

بدلًا من ذلك، عند استدعاء to_yaml على أي كائن، فسيحوله إلى صياغة YAML. ويمكنك تمرير هذا الكائن المحوّل إلى التابع المساعد simple_format لتنسيق الإخراج؛ وهذه هي وصفة المنقح السريَّة:

<%= simple_format @article.to_yaml %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

ستصدّر الشيفرة البرمجيّة السابقة شيئًا مشابهًا لما يلي:

--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: It's a very helpful guide for debugging your Rails app.
title: Rails debugging guide
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}
 
Title: Rails debugging guide

inspect

هنالك تابع آخر مفيد لعرض قيم الكائن، خاصةً عند العمل مع المصفوفات أو جداول Hash، وسيطبع هذا قيمة الكائن كسلسلة نصيّة؛ فعلى سبيل المثال:

<%= [1, 2, 3, 4, 5].inspect %>
<p>
  <b>Title:</b>
  <%= @article.title %>
</p>

سيصيَّر إلى:

[1, 2, 3, 4, 5]
 
Title: Rails debugging guide

المسجل (The Logger)

من المفيد أيضًا حفظ المعلومات لتسجيل الملفات وقت التشغيل، لذا يحتفظ ريلز بملف سجل منفصل لكل بيئة تشغيل.

ما هو المسجل؟

يستخدم ريلز الصنف ActiveSupport::Logger لكتابة معلومات السجل، ويمكنك استبدال هذا بمسجلات (loggers) أخرى مثل Log4r.

يمكنك تحديد مسجّل بديل في الملف config/application.rb أو في أي ملف بيئة آخر؛ على سبيل المثال:

config.logger = Logger.new(STDOUT)
config.logger = Log4r::Logger.new("Application Log")

أو في القسم Initializer، أضف أي واحدة مما يلي:

Rails.logger = Logger.new(STDOUT)
Rails.logger = Log4r::Logger.new("Application Log")

تنبيه: بشكل افتراضي، تُنشَأ السجلات في Rails.root/log/‎ ويسمى ملف السجل بعد اسم البيئة التي يشتغل فيها التطبيق.

مستويات السجل

عند تسجيل شيء ما، فإنه يطّبع في السجل المطابق إذا كان مستوى السجل للرسالة مساويًا أو أعلى من مستوى السجل الذي تمت تهيئته. إذا كنت تريد معرفة مستوى السجل الحالي، يمكنك استدعاء التابع Rails.logger.level.

مستويات السجل المتوفّرة هي: ‎:debug، و ‎:info، و ‎:warn، و ‎:error، و ‎:fatal، و ‎:unknown التي تقابل أرقام مستويات السجل من 0 إلى 5 على التوالي. ولتغيير مستوى السجل الافتراضي، استخدم:

config.log_level = :warn # In any environment initializer, or
Rails.logger.level = 0 # at any time

هذا مفيد عندما تريد التسجيل في بيئة التطوير (development) أو بيئة التحضير للإطلاق (staging) دون إغراق السجل في بيئة الإنتاج (production) بالمعلومات غير الضروريّة.

تنبيه: مستوى سجل ريلز هو debug في كافة البيئات.

إرسال الرسائل

استخدم التابع logger.(debug|info|warn|error|fatal)‎ للكتابة في السجل الحالي من داخل المتحكم، أو النموذج، أو الارسال:

logger.debug "Person attributes hash: #{@person.attributes.inspect}"
logger.info "Processing the request..."
logger.fatal "Terminating application, raised unrecoverable error!!!"

في ما يلي مثال على تابع مصمّم بتسجيل إضافي:

class ArticlesController < ApplicationController
  # ...
 
  def create
    @article = Article.new(article_params)
    logger.debug "New article: #{@article.attributes.inspect}"
    logger.debug "Article should be valid: #{@article.valid?}"
 
    if @article.save
      logger.debug "The article was saved and now the user is going to be redirected..."
      redirect_to @article, notice: 'Article was successfully created.'
    else
      render :new
    end
  end
 
  # ...
 
  private
    def article_params
      params.require(:article).permit(:title, :body, :published)
    end
end

في ما يلي مثال للسجل المولد عند تنفيذ إجراء هذا المتحكم:

Started POST "/articles" for 127.0.0.1 at 2017-08-20 20:53:10 +0900
Processing by ArticlesController#create as HTML
  Parameters: {"utf8"=>"✓", "authenticity_token"=>"xhuIbSBFytHCE1agHgvrlKnSVIOGD6jltW2tO+P6a/ACjQ3igjpV4OdbsZjIhC98QizWH9YdKokrqxBCJrtoqQ==", "article"=>{"title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>"0"}, "commit"=>"Create Article"}
New article: {"id"=>nil, "title"=>"Debugging Rails", "body"=>"I'm learning how to print in logs!!!", "published"=>false, "created_at"=>nil, "updated_at"=>nil}
Article should be valid: true
   (0.1ms)  BEGIN
  SQL (0.4ms)  INSERT INTO "articles" ("title", "body", "published", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["title", "Debugging Rails"], ["body", "I'm learning how to print in logs!!!"], ["published", "f"], ["created_at", "2017-08-20 11:53:10.010435"], ["updated_at", "2017-08-20 11:53:10.010435"]]
   (0.3ms)  COMMIT
The article was saved and now the user is going to be redirected...
Redirected to http://localhost:3000/articles/1
Completed 302 Found in 4ms (ActiveRecord: 0.8ms)

إن إضافة تسجيلًا إضافيًّا مثل هذا يسهّل علينا البحث عن سلوك غير متوقّع أو غير معتاد في سجلاتك. إذا أضفت سجلًا إضافيًا، فتأكد من استخدام مستويات السجل بشكل معقول لتجنّب ملء سجلات الإنتاج الخاصة بك بتفاصيل غير مفيدة.

تسجيل العلامات

عند تسجيل تطبيقات متعدّدة المستخدمين ومتعدّدة الحسابات، من المفيد عادةً تصفية السجلات باستخدام بعض القواعد المخصّصة. يساعد TaggedLogging في Active Support على القيام بذلك عن طريق ختم أسطر السجل بنطاقات فرعيّة ومعرّفات الطلب وأي شيء آخر للمساعدة في تنقيح مثل هذه التطبيقات.

logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged("BCX") { logger.info "Stuff" }                            # Logs "[BCX] Stuff"
logger.tagged("BCX", "Jason") { logger.info "Stuff" }                   # Logs "[BCX] [Jason] Stuff"
logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Stuff" } } # Logs "[BCX] [Jason] Stuff"

تأثير السجلات على الأداء

للتسجيل تأثير بسيط على أداة تطبيق ريلز، خاصةً عند التسجيل في القرص (disk)؛ بالإضافة إلى ذلك، هنالك بعض الخفايا الأخرى:

باستخدام المستوى ‎:debug، سيحتوي على عقوبة أداء (performance penalty) أكبر من ‎:fatal إذ يقيّم ويكتب عدد كبير من السلاسل النصيّة إلى مخرجات السجل (إلى القرص الصلب مثلًا).

هنالك خطر آخر محتمل يتمثّل في عدد كبير جدًا من الاستدعاءات إلى Logger في شيفرتك البرمجيّة:

logger.debug "Person attributes hash: #{@person.attributes.inspect}"

في المثال أعلاه، سيكون هنالك تأثير على الأداء حتى إذا لم يتضمّن مستوى الإخراج المسموح به على debug، والسبب هو أن على روبي أن يقيّم هذه السلاسل النصيّة، والتي تتضمّن استنباط كائن سلسلة نصيّة ثقيل واستيفاء المتغيّرات؛ ولذلك، يُنصح بتمرير الكتل إلى التوابع logger، حيث تقيّم فقط إذا كان مستوى المخرجات هو نفس المستوى - أو مضمّن فيه - المسموح به (على سبيل المثال التحميل الكسول [lazy loading]). وسيكون شكل الشيفرة البرمجيّة المعاد كتابتها كالتالي:

logger.debug {"Person attributes hash: #{@person.attributes.inspect}"}

تقيّم محتويات الكتلة، ومن ثم استيفاء السلسلة النصيّة فقط في حالة تفعيل debug، و لا يمكن ملاحظة توفير الأداء هذا إلا مع كميّات كبيرة من التسجيلات، ولكنه من الممارسات الجيّد توظيفها.

التنقيح باستخدام الجوهرة byebug

عندما تتصرّف شيفرتك البرمجيّة بطرق غير متوقعة، يمكنك محاولة الطباعة إلى السجلات أو إلى الطرفية لتشخيص المشكلة. ولسوء الحظ، هنالك أوقات يكون فيها هذا النوع من التتبّع غير فعال في العثور على السبب الأساسي لمشكلة ما، وعندها ستحتاج فعليًا إلى الانتقال إلى شيفرة برمجية قيد التشغيل، وسيكون المنقّح هو أفضل رفيق.

يمكن أن يساعدك المنقح أيضًا إذا كنت تريد التعرّف على التعليمات البرمجيّة المصدرية لريلز ولكن لا تعرف من أين تبدأ، نقّح فقط أي طلب إلى تطبيقك الخاص واستخدم هذا الدليل لتتعلّم كيف تتنقّل من الشيفرة التي كتبتها إلى شيفرة ريلز الأساسيّة.

الإعداد

يمكنك استخدام الجوهرة byebug لتعيين نقاط التوقف والتقدم بشكل حي في ريلز؛ ولتثبيتها، نفذ الأمر التالي:

$ gem install byebug

يمكنك استدعاء المنقّح عن طريق استدعاء التابع byebug في داخل أي تطبيق ريلز. وهذا مثال على ذلك:

class PeopleController < ApplicationController
  def new
    byebug
    @person = Person.new
  end
end

الصدفة (Shell)

بمجرّد أن يستدعي تطبيقك الخاص التابع byebug، سيبدأ المنقّح في صَدَفة التنقيح داخل نافذة الطرفيّة حيث قمت بتشغيل خادم تطبيقك الخاص ومن ثم ستجد نفسك في محث المنقح (byebug).

قبل المحث (prompt)، ستُعّرض الشيفرة البرمجيّة الموجودة حول السطر الذي سيُنفَّذ، وسيميّز السطر الحالي عن طريق '‎=>‎' كالتالي:

[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }
 
(byebug)

إذا وصلت إلى هناك بواسطة طلب متصفّح، فستتعلّق علامة تبويب المتصفّح التي تحتوي على الطلب حتى إنتهاء منقّح الأخطاء وانتهاء المتتبّع من معالجة كامل الطلب. مثال على ذلك:

> Booting Puma
=> Rails 5.1.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.4.0 (ruby 2.3.1-p112), codename: Owl Bowl Brawl
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200
  ActiveRecord::SchemaMigration Load (0.2ms)  SELECT "schema_migrations".* FROM "schema_migrations"
Processing by ArticlesController#index as HTML
 
[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }
(byebug)

حان الوقت الآن لاستكشاف تطبيقك؛ ويمكنك البدء عن طريق طلب المساعدة من منقّح الأخطاء. اكتب help بالشكل:

(byebug) help
 
  break      -- Sets breakpoints in the source code
  catch      -- Handles exception catchpoints
  condition  -- Sets conditions on breakpoints
  continue   -- Runs until program ends, hits a breakpoint or reaches a line
  debug      -- Spawns a subdebugger
  delete     -- Deletes breakpoints
  disable    -- Disables breakpoints or displays
  display    -- Evaluates expressions every time the debugger stops
  down       -- Moves to a lower frame in the stack trace
  edit       -- Edits source files
  enable     -- Enables breakpoints or displays
  finish     -- Runs the program until frame returns
  frame      -- Moves to a frame in the call stack
  help       -- Helps you using byebug
  history    -- Shows byebug's history of commands
  info       -- Shows several informations about the program being debugged
  interrupt  -- Interrupts the program
  irb        -- Starts an IRB session
  kill       -- Sends a signal to the current process
  list       -- Lists lines of source code
  method     -- Shows methods of an object, class or module
  next       -- Runs one or more lines of code
  pry        -- Starts a Pry session
  quit       -- Exits byebug
  restart    -- Restarts the debugged program
  save       -- Saves current byebug session to a file
  set        -- Modifies byebug settings
  show       -- Shows byebug settings
  source     -- Restores a previously saved byebug session
  step       -- Steps into blocks or methods one or more times
  thread     -- Commands to manipulate threads
  tracevar   -- Enables tracing of a global variable
  undisplay  -- Stops displaying all or some expressions when program stops
  untracevar -- Stops tracing a global variable
  up         -- Moves to a higher frame in the stack trace
  var        -- Shows variables and its values
  where      -- Displays the backtrace
 
(byebug)

لرؤية الأسطر العشرة السابقة يجب عليك كتابة list-‎ (أو l-‎):

(byebug) l-
 
[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
   1  class ArticlesController < ApplicationController
   2    before_action :set_article, only: [:show, :edit, :update, :destroy]
   3
   4    # GET /articles
   5    # GET /articles.json
   6    def index
   7      byebug
   8      @articles = Article.find_recent
   9
   10     respond_to do |format|

وبهذه الطريقة يمكنك الانتقال داخل الملف ورؤية الشيفرة البرمجيّة أعلى السطر الذي أضفت إليه الاستدعاء byebug. وفي النهاية، لمعرفة مكان وجودك في الشيفرة البرمجيّة، يمكنك كتابة list=‎:

(byebug) list=
 
[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }
(byebug)

السياق

عند بدء تنقيح تطبيقك الخاص، ستوضع في سياقات مختلفة أثناء التنقّل بين الأجزاء المختلفة من الحزمة.

يُنشئ المنقّح سياقًا عند الوصول إلى نقطة توقّف أو حدث. يحتوي السياق على معلومات حول البرنامج المعلّق الذي يمكّن منقح الأخطاء من فحص مكدس الإطار (frame stack)، ومعرفة المكان الذي توقف في البرنامج المنقح.

ويمكنك في أي وقت استدعاء الأمر backtrace (أو الاسم البديل where) لطباعة التتبع العكسي للتطبيق. ويمكن أن يكون هذا مفيدًا للغاية لمعرفة كيف وصلت إلى هذا المكان، لذا إذا تساءلت يومًا عن كيفية وصولك على مكان ما في شيفرتك البرمجيّة، فسيوفّر backtrace لك الإجابة.

(byebug) where
--> #0  ArticlesController.index
      at /PathToProject/app/controllers/articles_controller.rb:8
    #1  ActionController::BasicImplicitRender.send_action(method#String, *args#Array)
      at /PathToGems/actionpack-5.1.0/lib/action_controller/metal/basic_implicit_render.rb:4
    #2  AbstractController::Base.process_action(action#NilClass, *args#Array)
      at /PathToGems/actionpack-5.1.0/lib/abstract_controller/base.rb:181
    #3  ActionController::Rendering.process_action(action, *args)
      at /PathToGems/actionpack-5.1.0/lib/action_controller/metal/rendering.rb:30

توضع العلامة ‎-->‎ على الإطار الحالي. ويمكنك التنقل في أي مكان تريده في هذا التتبع (وبالتالي تغيير السياق) عن طريق استخدام الأمر frame n، حيث أن n هو رقم الإطار المحدد، وسيعّرض byebug السياق الجديد الخاص بك.

(byebug) frame 2
 
[176, 185] in /PathToGems/actionpack-5.1.0/lib/abstract_controller/base.rb
   176:       # is the intended way to override action dispatching.
   177:       #
   178:       # Notice that the first argument is the method to be dispatched
   179:       # which is *not* necessarily the same as the action name.
   180:       def process_action(method_name, *args)
=> 181:         send_action(method_name, *args)
   182:       end
   183:
   184:       # Actually call the method associated with the action. Override
   185:       # this method if you wish to change how action methods are called,
(byebug)

المتغيرات المتوفّرة هي نفسها كما لو كنت تشغّل الشيفرة البرمجيّة سطرًا بعد سطر، فهذا هو التنقيح.

يمكنك أيضًا استخدام الأمرين up [n]‎ و down [n]‎ لتغيير الإطارات n للسياق إلى أعلى أو أسفل المكدس على التوالي، و n تساوي 1 بشكل افتراضي. وفي هذه الحالة، تستخدم Up للذهاب إلى إطارات المكدس ذات الرقم الأعلى و Down لإطارات المكدس ذات الرقم الأدنى.

الخيوط

يمكن للمنقّح سرد، وإيقاف، واستئناف التبديل بين الخيوط قيد العمل باستخدام الأمر thread (أو الاسم البديل th)، ويحتوي هذا الأمر على مجموعة من الخيارات المفيدة:

  • thread: عرض الخيط الحالي.
  • thread list: لعرض جميع الخيوط وحالتهم، ستكون هنالك علامة (+) على الخيط الحالي.
  • thread stop n: لإيقاف الخيط n.
  • thread resume n: لاستئناف الخيط n.
  • thread switch n: لتبديل سياق الخيط الحالي إلى n.

هذا الأمر مفيد للغاية عند تنقيح الخيوط المتزامنة التي تحتاج إلى التحقق من عدم وجود حالات تسابق (race conditions) في شيفرتك البرمجيّة.

فحص المتغيرات

يمكن تقييم أي تعبير في السياق الحاليط؛ فلتقييم تعبير، اكتبه فقط!

يوضح لك هذا المثال كيف يمكنك طباعة متغيّرات النسخة المعرفة في السياق الحالي:

[3, 12] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }
 
(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context,
 :@_action_name, :@_response_body, :@marked_for_same_origin_verification,
 :@_config]

ربما قد لاحظت عرض جميع المتغيّرات التي يمكن الوصول إليها من المتحكم وتُحدّث هذه القائمة بشكل حيوي أثناء تنفيذ الشيفرات البرمجيّة. على سبيل المثال، شغّل السطر التالي باستخدام next (ستتعرّف أكثر على هذا الأمر لاحقًا في هذا الدليل).

(byebug) next
 
[5, 14] in /PathTo/project/app/controllers/articles_controller.rb
   5     # GET /articles.json
   6     def index
   7       byebug
   8       @articles = Article.find_recent
   9
=> 10      respond_to do |format|
   11        format.html # index.html.erb
   12        format.json { render json: @articles }
   13      end
   14    end
   15
(byebug)

ثم أطلب مرة أخرى instance_variables:

(byebug) instance_variables
[:@_action_has_layout, :@_routes, :@_request, :@_response, :@_lookup_context,
 :@_action_name, :@_response_body, :@marked_for_same_origin_verification,
 :@_config, :@articles]

يجري تضمين ‎@articles إلى متغيرات النسخة، لأنه تم تنفيذ السطر الذي يُعرّف به.

تنبيه: يمكنك الذهاب إلى الوضع irb مع الأمر irb (بالطبع)، وسيؤدي هذا إلى بدء جلسة irb ضمن السياق الذي تستدعيها به.

إن أفضل طريقة لعرض المتغيرات وقيمهم هي عن طريق استخدام التابع var؛ دعنا نستخدم byebug لمساعدتنا على ذلك:

(byebug) help var
 
  [v]ar <subcommand>
 
  Shows variables and its values
 
 
  var all      -- Shows local, global and instance variables of self.
  var args     -- Information about arguments of the current scope
  var const    -- Shows constants of an object.
  var global   -- Shows global variables.
  var instance -- Shows instance variables of self or a specific object.
  var local    -- Shows local variables in current scope.

هذه طريقة رائعة لفحص قيم متغيّرات السياق الحالي؛ فعلى سبيل المثال، للتأكد من أنه لا يوجد متغيّرات محليّة معرّفة حاليًا:

(byebug) var local
(byebug)

يمكنك أيضًا فحص تابع كائن بهذه الطريقة:

(byebug) var instance Article.new
@_start_transaction_state = {}
@aggregation_cache = {}
@association_cache = {}
@attributes = #<ActiveRecord::AttributeSet:0x007fd0682a9b18 @attributes={"id"=>#<ActiveRecord::Attribute::FromDatabase:0x007fd0682a9a00 @name="id", @value_be...
@destroyed = false
@destroyed_by_association = nil
@marked_for_destruction = false
@new_record = true
@readonly = false
@transaction_state = nil

يمكنك أيضًا استخدام display لبدء مشاهدة متغيّراتك، وهذه طريقة جيّدة لتتبّع قيم المتغير أثناء التنفيذ.

(byebug) display @articles
1: @articles = nil

ستُطبع المتغيرات داخل القائمة المعروضة بقيمها بعد تنقلك داخل المكدّس. ولإيقاف عرض المتغيّر، استخدم undisplay n حيث أن n هو رقم المتغيّر (1 في المثال الأخير).

خطوة بخطوة

يجب عليك الآن أن تعرف أين مكانك في التتبّع وكيف تطبع المتغيّرات المتاحة، ولكن دعنا نستمر ونتقدّم في تنفيذ التطبيق.

استخدم step (واسمها البديل s) لمتابعة تشغيل البرنامج حتى نقطة التوقف المنطقيّة التالية وإرجاع التحكم للمنقّح.

إن next تشبه step، إلا أن step تتوقف في السطر التالي من الشيفرة البرمجية التي تُنفّذ، وهي تقوم بخطوة واحدة فقط. وأما next، فتنتقل إلى السطر التالي دون الدخول إلى التوابع.

فعلى سبيل المثال، فكّر في الموقع التالي:

Started GET "/" for 127.0.0.1 at 2014-04-11 13:39:23 +0200
Processing by ArticlesController#index as HTML
 
[1, 6] in /PathToProject/app/models/article.rb
   1: class Article < ApplicationRecord
   2:   def self.find_recent(limit = 10)
   3:     byebug
=> 4:     where('created_at > ?', 1.week.ago).limit(limit)
   5:   end
   6: end
 
(byebug)

إذا استخدمنا next، فلن نتعمّق في استدعاءات التابع، وبدل من ذلك، سيذهب byebug إلى السطر التالي في نفس السياق؛ وفي هذه الحالة، يكون السطر الأخير من التابع الحالي، ولذا سيعود byebug إلى السطر التالي التابع المستدعي (caller method).

(byebug) next
[4, 13] in /PathToProject/app/controllers/articles_controller.rb
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     @articles = Article.find_recent
    8:
=>  9:     respond_to do |format|
   10:       format.html # index.html.erb
   11:       format.json { render json: @articles }
   12:     end
   13:   end
 
(byebug)

إذا استخدمنا step في نفس الموّقف، فستتوجّه byebug حرفيًا إلى تعليمة روبي التالية المراد تنفيذها، والتي هي في هذه الحالة التابع week الذي يخص Active Support.

(byebug) step
 
[49, 58] in /PathToGems/activesupport-5.1.0/lib/active_support/core_ext/numeric/time.rb
   49:
   50:   # Returns a Duration instance matching the number of weeks provided.
   51:   #
   52:   #   2.weeks # => 14 days
   53:   def weeks
=> 54:     ActiveSupport::Duration.weeks(self)
   55:   end
   56:   alias :week :weeks
   57:
   58:   # Returns a Duration instance matching the number of fortnights provided.
(byebug)

هذه هي واحدة من أفضل الطرق للعثور على العلل في شيفرتك البرمجيّة.

ملاحظة: يمكنك استخدام step n أو next n للتقدم بـ n خطوة في المرة الواحدة.

نقاط التوقف

توقّف نقاط التوقف تطبيقك عند الوصول إلى نقطة معيّنة في البرنامج، وتُستدعى صدفة debugger إلى هذا السطر.

يمكنك إضافة نقاط توقّف بشكل حيوي مع الأمر break (أو b فقط) وهنالك ثلاثة طرائق ممكنة لإضافة نقاط التوقف يدويًا:

  • break n: إضافة نقطة توقّف في السطر n في الملف المصدري الحالي.
  • break file:n [if expression]‎: إضافة نقطة توقف في السطر n داخل الملف file إذا كان التعبير expression صحيحًا في المنقح.
  • break class(.|\#)method [if expression]‎: ضع نقطة التوقف (. و # للصنف و تابع النسخة على التوالي) في التابع المعرّف في الصنف. ويعمل هذا التعبير بنفس الطريقة كما في file:n.

فعلى سبيل المثال، في الحالة السابقة:

[4, 13] in /PathToProject/app/controllers/articles_controller.rb
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     @articles = Article.find_recent
    8:
=>  9:     respond_to do |format|
   10:       format.html # index.html.erb
   11:       format.json { render json: @articles }
   12:     end
   13:   end
 
(byebug) break 11
Successfully created breakpoint with id 1

استخدم info breakpoints لعرض نقاط التوقّف. وإذا أضفت رقمًا، فسيعرض نقطة التوقف صاحبة هذا العدد. خلاف ذلك، ستعرض جميع نقاط التوقف.

(byebug) info breakpoints
Num Enb What
1   y   at /PathToProject/app/controllers/articles_controller.rb:11

لحذف نقاط التوقف، استخدم الأمر delete n لحذف نقطة التوقف ذات الرقم n. وإذا لم تحدد رقمًا معيّنًّا، فستحذف جميع نقاط التوقف النشطة حاليًا.

(byebug) delete 1
(byebug) info breakpoints
No breakpoints.

يمكنك أيضا تفعيل أو تعطيل نقاط التوقف:

  • enable breakpoints [n [m [...]]]‎: تسمح لمجموعة من نقاط التوقف أو جميعها بإيقاف البرنامج، وهذه هي الحالة الافتراضيّة عند إنشاء نقطة توقّف.
  • disable breakpoints [n [m [...]]]‎: تسمح بإيقاف تأثير مجموعة أو جميع نقاط التوقف في برنامجك.

اصطياد الاستثناءات

يمكن للأمر catch exception-name (أو cat exception-name فقط) اعتراض الاستثناء من نوع exception-name عندما يكون موجودًا. خلاف ذلك، لن يكون له معالجًا.

استخدم catch لعرض جميع نقاط الاعتراض النشطة (active catchpoints).

استئناف التنفيذ

هنالك طريقتان لاستئناف تنفيذ أحد التطبيقات المتوقّفة في المنقح هما:

  • continue [n]‎: يستأنف تنفيذ البرنامج في العنوان الذي توقّف فيه، وستتجاوز أي نقاط توقف موجودة في هذا العنوان. يسمح لك المعامل الإختياري n بتحديد رقم السطر لتعيين نقطة توقف لمرة واحدة تحذف عند الوصول إليها.
  • finish [n]‎: تنفيذ البرنامج حتى إرجاع إطار المكدّس المحدّد. إذا لم يعطى رقم الإطار، فسيستمر التطبيق بالعمل حتى يُرجع الإطار المحدد الحالي. ويبدأ الإطار المختار حاليًا بالإطار الأكثر حداثة أو 0 إذا لم يتم عمل أي إطار (على سبيل المثال up أو down أو frame). وإذا مُرّر رقم الإطار، فسيعمل حتى يُرجع الإطار المحدّد.

التعديل

هنالك أمران يسمحان لك بفتح الشيفرة البرمجيّة من المنقّح إلى المحرّر:

  • edit [file:n]‎: تعديل الملف file باستخدام المحرّر المحدد بواسطة متغيّر البيئة EDITOR ويمكن إعطاء سطر محدّد عبر n.

الخروج

للخروج من المنقّح، استخدم الأمر quit (أو q) أو اكتب q!‎ لتجاوز المحث Really quit? (y/n)‎ والخروج دون قيد أو شرط.

ينهي الخروج البسيط جميع الخيوط قيد التنفيذ، ولذلك سيتوقّف الخادم الخاص بك وسيتعيّن عليك تشغيله مرّة أخرى.

الإعدادات

لدى byebug بعض الخيارات المتاحة لتعديل سلوكه:

(byebug) help set
 
  set <setting> <value>
 
  Modifies byebug settings
 
  Boolean values take "on", "off", "true", "false", "1" or "0". If you
  don't specify a value, the boolean setting will be enabled. Conversely,
  you can use "set no<setting>" to disable them.
 
  You can see these environment settings with the "show" command.
 
  List of supported settings:
 
  autosave       -- Automatically save command history record on exit
  autolist       -- Invoke list command on every stop
  width          -- Number of characters per line in byebug's output
  autoirb        -- Invoke IRB on every stop
  basename       -- <file>:<line> information after every stop uses short paths
  linetrace      -- Enable line execution tracing
  autopry        -- Invoke Pry on every stop
  stack_on_error -- Display stack trace when `eval` raises an exception
  fullpath       -- Display full file names in backtraces
  histfile       -- File where cmd history is saved to. Default: ./.byebug_history
  listsize       -- Set number of source lines to list by default
  post_mortem    -- Enable/disable post-mortem mode
  callstyle      -- Set how you want method call parameters to be displayed
  histsize       -- Maximum number of commands that can be stored in byebug history
  savefile       -- File where settings are saved to. Default: ~/.byebug_save

ملاحظة: يمكنك حفظ هذه الإعدادات في الملف ‎.byebugrc في المجلّد المنزل لديك (أي المجلد home). وسيقرأ المنقّح هذه الإعدادات العامة عند بدء تشغيله، وهذا مثال على ذلك:

set callstyle short
set listsize 25

التنقيح باستخدام الجوهرة web-console

تشبه طرفية الويب byebug قليلًا، لكنها تعمل في المتصفّح. ففي أي صفحة تطوّرها، يمكنك طلب طرفية (console) في سياق العرض أو المتحكم، وستصيَّر الطرفية بجانب محتوى HTML الخاص بك.

الطرفية

يمكنك استدعاء الطرفية داخل أي إجراء متحكم أو عرض عن طريق استدعاء التابع console.

فعلى سبيل المثال، في المتحكم:

class PostsController < ApplicationController
  def new
    console
    @post = Post.new
  end
end

أو في العرض:

<% console %>
 
<h2>New Post</h2>

هذا سيصيِّر طرفيةً داخل العرض الخاص بك. لا تحتاج إلى الإهتمام بمكان استدعاء console، فلن تصيَّر في مكان استدعائها بل بعد محتوى HTML.

تنفّذ الطرفية شيفرات روبي الصرفة: يمكنك تعريف وتمثيل الأصناف المخصّصة، وإنشاء نماذج جديدة وفحص المتغيّرات.

ملاحظة: يمكن تصيير طرفية واحدة فقط لكل طلب، وإلا سيرمي web-console خطأً عند استدعاء الطرفية الثانية.

فحص المتغيرات

يمكنك استدعاء instance_variables لعرض جميع متغيرات النسخة الموجودة في السياق الخاص بك. إذا كنت ترغب في عرض جميع المتغيّرات المحليّة، فيمكنك فعل ذلك باستخدام local_variables.

الإعدادات

  • config.web_console.whitelisted_ips: السماح لقائمة من عناوين IPv4 أو IPv6 و شبكات (بشكل افتراضي 127.0.0.1/8, ::1).
  • config.web_console.whiny_requests: تسجيل رسالة عند منع تصيير طرفية (تساوي true بشكل افتراضي ).

نظرًا لتقييم web-console للشيفرة البرمجيّة الخاصة بروبي عن بعد في الخادم، فلا تحاول استخدامه في الإنتاج.

تنقيح تسريب الذاكرة

يمكن أن يتسبب تطبيق روبي (ريلز أو تطبيق روبي صرف) في تسرب للذاكرة، إما على مستوى شيفرة روبي أو على مستوى شيفرة C.

في هذا القسم، سنتعلّم، كيفيّة العثور على مثل هذه التسريبات وإصلاحها باستخدام أداة مثل Valgrind.

Valgrind

إنَّ Valgrind هو تطبيق للكشف عن تسرّب الذاكرة المستندة على C.

هنالك أدوات Valgrind التي يمكنها الكشف تلقائيًا عن العديد من الأخطاء في إدارة الذاكرة والخيوط، ووضع برنامجك بالتفصيل. على سبيل المثال، إذا كان ملحق C في المترجم (interpreter) يستدعي malloc()‎ لكنه لا يستدعي free()‎ بشكل صحيح، فلن تتوفّر هذه الذاكرة حتى ينتهي التطبيق.

لمزيد من المعلومات حول كيفيّة تثبيت Valgrind واستخدام روبي، راجع هذه المقالة بواسطة Evan Weaver.

إضافات للتنقيح

هنالك بعض إضافات ريلز لمساعدتك في العثور على الأخطاء وتنقيح تطبيقك، وفي ما يلي قائمة بالملحقات المفيدة في التنقيح:

  • Footnotes: كل صفحة ريلز تملك حواشي سفليّة تعطي معلومات حول الطلب وتربطها بالمصدر الخاص بك عبر TextMate.
  • Query Trace: يضيف أصل الاستعلام إلى سجلاتك.
  • Query Reviewer: لن تشغل هذه الإضافة "EXPLAIN" فقط قبل كل الاستعلامات التي حددتها في التطوير، ولكنها توفّر عنصر <div> صغير في المخرجات المصيَّرة لكل صفحة مع ملخّص للتحذيرات لكل استعلام تم تحليله.
  • Exception Notifier: يوفر كائن مرسل بريد (mailer) ومجموعة من القوالب الافتراضيّة لإرسال إشعارات البريد الإلكتروني عند حدوث أخطاء في تطبيق ريلز.
  • Better Errors: تستبدل هذه الإضافة صفحة خطأ ريلز الافتراضيّة بأخرى جديدة تحتوي على المزيد من المعلومات السياقيّة مثل الشيفرة المصدريّة وفحص المتغيّر.
  • RailsPanel: إضافة كروم لتطوير ريلز والتي ستضيف إلى development.log، والحصول على جميع المعلومات حول استعلامات تطبيق ريلز في المتصفّح - في لوحة أدوات المطوّر. ويقدّم إحصاءات إلى db/rendering/total للأوقات وقائمة المعاملات والعروض المصيَّرة والمزيد.
  • Pry: بديل لـ IRB وطرفية وقت التشغيل للمطوّر.

مصادر