تنقيح تطبيقات ريلز
يقدّم هذا الدليل التقنيات اللازمة لتنقيح تطبيقات ريلز. بعد قراءة هذا الدليل، ستتعلّم:
- الغرض من التنقيح.
- كيفة تتبّع العلل والأخطاء التي لا تتعرّف عليها الاختبارات في تطبيقك.
- طرقًا مختلفة للتنقيح.
- كيفيّة تحليل أثر المكدس.
مساعدي العرض للتنقيح
احدى المهام الشائعة في التنقيح هي فحص محتويات متغيّر معيّن، لذا يوفر ريلز ثلاثة طرائق لفعل ذلك:
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
في الدعم الفعال على القيام بذلك عن طريق ختم أسطر السجل بنطاقات فرعيّة ومعرّفات الطلب وأي شيء آخر للمساعدة في تنقيح مثل هذه التطبيقات.
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
الخاص بالدعم الفعال.
(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 وطرفية وقت التشغيل للمطوّر.