البحث في الموقع
المحتوى عن 'airbnb'.
-
إن هذا الدليل مستوحى من دليل GitHub ودليل Bozhidar Batsov. ولدينا أيضًا دليل جافاسكربت لتنسيق الشيفرة البرمجية جدول المحتويات المسافات البيضاء المسافة البادئة في الشيفرة المسافات السطرية الأسطر الجديدة طول السطر التعليقات تعليقات على مستوى الملف/الصنف تعليقات الدالة التعليقات الكتلية والمضمّنة علامات الترقيم والإملاء والنحو تعليقات TODO شيفرات برمجية بدون تعليقات الدوال تعريف الدوال دوال الاستدعاء التعابير الشرطيّة الكلمات المفتاحية للجمل الشرطية المعامل الثلاثي الشروط المتشعّبة الصياغة قواعد التسمية الأصناف الاستثناءات التجميعات السلاسل النصية التعابير النمطية محارف النسبة المئوية ريلز نطاقات انسجم مع الشيفرة المسافات البيضاء المسافة البادئة في الشيفرة استخدم الزر (tabs) بمقدار مسافتين (وتسمى أيضًا Soft-Tab نظرًا لأن زر Tab الإفتراضي يكون ثمان مسافات). سيكون عمق الكلمة المفتاحية when بقدر عمق case. case when song.name == 'Misty' puts 'Not again!' when song.duration > 120 puts 'Too long!' when Time.now.hour > 21 puts "It's too late" else song.play end kind = case year when 1850..1889 then 'Blues' when 1890..1909 then 'Ragtime' when 1910..1929 then 'New Orleans Jazz' when 1930..1939 then 'Swing' when 1940..1950 then 'Bebop' else 'Jazz' end حاذِ وسطاء الّدالة فإما أن يكونوا على نفس السطر أو سطر لكلّ واحدٍ منهم. # bad def self.create_translation(phrase_id, phrase_key, target_locale, value, user_id, do_xss_check, allow_verification) ... end # good def self.create_translation(phrase_id, phrase_key, target_locale, value, user_id, do_xss_check, allow_verification) ... end # good def self.create_translation( phrase_id, phrase_key, target_locale, value, user_id, do_xss_check, allow_verification ) ... end حاذِ الأسطر المتتابعة بمقدار مسافتين في جملة التنفيذ للتعابير المنطقية متعددة الأسطر. # bad def is_eligible?(user) Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) && is_in_program?(user) && program_not_expired end # good def is_eligible?(user) Trebuchet.current.launch?(ProgramEligibilityHelper::PROGRAM_TREBUCHET_FLAG) && is_in_program?(user) && program_not_expired end المسافات السطرية لا تترك مسافة زائدة. عند تضمين التعليقات في السطر، اترك مسافة واحدة بين نهاية الشيفرة البرمجية وبداية تعليقك. # bad result = func(a, b)# we might want to change b to c # good result = func(a, b) # we might want to change b to c استخدم المسافات حول المعاملات (operators)، وبعد الفواصل، وبعد النقطتين، وبعد الفاصلة المنقوطة، وبعد فتح قوس ما { وقبل إغلاقه أيضًا }. sum = 1 + 2 a, b = 1, 2 1 > 2 ? true : false; puts 'Hi' [1, 2, 3].each { |e| puts e } لا تضع أبدًا مسافة فارغة قبل الفاصلة. result = func(a, b) لا تضع مسافة داخل كتلة وسطاء الأنبوب (pipe)، بل ضع واحدًا بين الوسطاء في الكتلة، وواحدًا آخر خارج كتلة وسطاء الأنبوب. # bad {}.each { | x, y |puts x } # good {}.each { |x, y| puts x } لا تضع مسافات بين إشارة التعجب ! ومعاملاتها. !something لا تترك فارغات بعد الأقواس من نوع ( و[ أو قبلها ) و]. some(arg).other [1, 2, 3].length تجنب وضع المسافات عند إنشاء السلاسل النصية. # bad var = "This #{ foobar } is interpolated." # good var = "This #{foobar} is interpolated." لا تستخدم مسافة إضافية في النطاق الحرفي (range literals). # bad (0 ... coll).each do |item| # good (0...coll).each do |item| الأسطر الجديدة أضف سطرًا جديدًا بعد الجملة الشرطية if ذات الشروط المتعددة لتصبح في عدة أسطر، ولتساعد في التفريق بين الشروط وجسم الشرط. if @reservation_alteration.checkin == @reservation.start_date && @reservation_alteration.checkout == (@reservation.start_date + @reservation.nights) redirect_to_alteration @reservation_alteration end أضف سطرًا جديدًا بعد نهاية الجمل الشرطية، والكتل، والبيانات …إلخ. if robot.is_awesome? send_robot_present end robot.add_trait(:human_like_intelligence) لا تضف أسطرًا جديدةً فارغةً بين المناطق ذات مسافات بادئة مختلفة (مثلًا حول أجزاء الصنف أو أجسام الوحدة). # bad class Foo def bar # body omitted end end # good class Foo def bar # body omitted end end أضف سطرًا جديدًا فارغًا بين الدوالّ. def a end def b end أضف سطرًا فارغًا لفصل البيانات المترابطة في الدوالّ لتشكيلّ فقرات منطقية داخلها. def transformorize_car car = manufacture(options) t = transformer(robot, disguise) car.after_market_mod! t.transform(car) car.assign_cool_name! fleet.add(car) car end أضف سطرًا فارغًا في نهاية كل ملف، ولا تضف أبدًا عدة أسطر. طول السطر حافظ على كلّ سطر من الشيفرة البرمجية ليكون بطول مناسب للقراءة، وابقِ طول الأسطر أقل من 100 محرف (إليك السبب) إلا إن كان لديك سبب وجيه لجعله أطول من ذلك. التعليقات كُتِبَ هذا الشرح باستعانة كبيرة من دليل Google لتنسيق الشيفرة البرمجية للغة C++ ولغة Python. تعليقات على مستوى الملف/الصنف عند التعريف عن صنف ما يجب أن يوجد تعليق مصاحب له يصف ماهيّة الصنف وكيفية استخدامه. يجب أن يملك الملف الذي لا يحتوي على أصناف، أو يحتوي على أكثر من صنف على تعليق في أعلاه يصف محتوياته. # Automatic conversion of one locale to another where it is possible, like # American to British English. module Translation # Class for converting between text between similar locales. # Right now only conversion between American English -> British, Canadian, # Australian, New Zealand variations is provided. class PrimAndProper def initialize @converters = { :en => { :"en-AU" => AmericanToAustralian.new, :"en-CA" => AmericanToCanadian.new, :"en-GB" => AmericanToBritish.new, :"en-NZ" => AmericanToKiwi.new, } } end ... # Applies transforms to American English that are common to # variants of all other English colonies. class AmericanToColonial ... end # Converts American to British English. # In addition to general Colonial English variations, changes "apartment" # to "flat". class AmericanToBritish < AmericanToColonial ... end يجب أن تملك جميع الملفات من بينهم ملفات البيانات والإعداد، على تعليقات على مستوى الملف. # List of American-to-British spelling variants. # # This list is made with # lib/tasks/list_american_to_british_spelling_variants.rake. # # It contains words with general spelling variation patterns: # [trave]led/lled, [real]ize/ise, [flav]or/our, [cent]er/re, plus # and these extras: # learned/learnt, practices/practises, airplane/aeroplane, ... sectarianizes: sectarianises neutralization: neutralisation ... تعليقات الدالة يجب أن يكون هنالك تعليقات عند تعريف كلّ دالة وتكون قبلها مباشرة لتصف ما تؤديه هذه الدالة وكيفية استخدامها، ويجب أن تكون هذه الملفات وصفية ("يُفتح الملف") بدلًا من أمرية ("افتح الملف"). التعليقات التي تصف الدالّة، لا تشرح ما تقوم به الدالّة خطوة بخطوة. عمومًا، لا تصف هذه التعليقات كيفية تنفيذ هذه الدالّة عملها حرفيًا، وإنما، يجب ترك هذه التعليقات لتتخلل الشيفرة البرمجية للدالّة. يجب أن تذكر كلُّ دالّة ما هي مدخلاتها ومخرجاتها، إلا إن كانت لا تستوف جميع المعايير التالية: غير مرئية من الخارج. قصيرة جدًا. واضحة وبديهية. يمكنك استخدام أي تنسيق تريده، في لغة Ruby، يوجد مخططين لتوثيق وشرح عمل الدالّة وهما TomDoc وYARD كما يمكنك أيضًا كتابة تعليقات موجزة: # Returns the fallback locales for the_locale. # If opts[:exclude_default] is set, the default locale, which is otherwise # always the last one in the returned list, will be excluded. # # For example: # fallbacks_for(:"pt-BR") # => [:"pt-BR", :pt, :en] # fallbacks_for(:"pt-BR", :exclude_default => true) # => [:"pt-BR", :pt] def fallbacks_for(the_locale, opts = {}) ... end التعليقات الكتلية والمضمّنة المكان الأخير للتعليقات هو في الأجزاء الصعبة من الشيفرة البرمجية، فعّلق عليها الآن إذا توجب عليك شرحها عند مراجعة القادمة للشيفرة البرمجية. يجب أن تحصل العمليات المعقدة على بضعة أسطر من التعليقات قبل بدء العمليات، بالنسبة للأجزاء غير الواضحة فإنها التعليقات ستكون في نهاية السطر. def fallbacks_for(the_locale, opts = {}) # dup() to produce an array that we can mutate. ret = @fallbacks[the_locale].dup # We make two assumptions here: # 1) There is only one default locale (that is, it has no less-specific # children). # 2) The default locale is just a language. (Like :en, and not :"en-US".) if opts[:exclude_default] && ret.last == default_locale && ret.last != language_from_locale(the_locale) ret.pop end ret end من ناحية أخرى، لا تصف طريقة عمل التعليمات البرمجية، افترض أن الشخص الذي يقرأ شيفرتك البرمجية يعرف اللغة البرمجية (وإن لم يكن ما تحاول القيام به) أفضل منك. وفي سياق متصل: لا تستخدم التعليقات الكتلية، فلا يمكن أن تسبقها بمسافة وليس من السهل رؤيتها كالتعليقات العادية. # bad =begin comment line another comment line =end # good # comment line # another comment line علامات الترقيم والإملاء والنحو اهتم بعلامات الترقيم، والإملاء، والنحو. فمن السهل قراءة التعليقات المكتوبة بعناية على عكس نظيرتها غير المكتوبة بعناية. ينبغي أن تكون التعليقات قابلة للقراءة كنص سردي، مع وجود الحروف الكبيرة وعلامات الترقيم المناسبة. في حالات كثيرة، تكون الجمل الكاملة قابلة للقراءة أكثر من الجمل القصيرة. يمكن للتعليقات القصيرة، مثل الّتي توضع في نهاية آخر سطر في شيفرة برمجية أن تكون أقل رسمية، لكن على كلّ الأحوال يجب أن تكون متسقة مع تنسيقك للتعليقات ككلّ. على الرغم من أنه من المحبط أن يشير مُراجِع لشيفرتك البرمجية لاستخدامك فاصلة عادية في مكان الفاصلة المنقوطة، لذا من المهم أن تحافظ على شيفرتك البرمجية لتكون بمستوى عالٍ من الوضوح وقابلية القراءة، وستُساعدنا علامات الترقيم والكتابة الإملائية الصحيحة واستخدام الصحيح للقواعد النحوية في تحقيق هذا الهدف. تعليقات TODO استخدم تعليقات TODO للشيفرات البرمجية المؤقتة، أو للحلّ قصير الأجل، أو الحلّ الجيد بما يكفي ولكنه ليس مثاليًا. يجب أن تتضمن تعليقات TODO على كلمة TODO بالأحرف الكبيرة متبوعة بالاسم الكامل للشخص الذي يمكنه توفير أفضل حل للمشكلة في الشيفرة المشار إليها من قِبل تعليق TODO بين قوسين. يمكنك إضافة النقطتين ومن ثمّ ضع تعليق يشرح ما يجب القيام به، فالغرض الرئيسي هو تنسيق تعليق TODO ليصبح قابلًا للبحث ليجده الشخص الّذي يمكنه توفير مزيد من التفاصيل عند الطلب. إن تعليق TODO لا يُلزم الشخص المشار إليه لإصلاح المشكلة، ولذلك عند إنشائك تعليق TODO، فغالبًا -إن لم يكن دائمًا- ستكتب اسمك. # bad # TODO(RS): Use proper namespacing for this constant. # bad # TODO(drumm3rz4lyfe): Use proper namespacing for this constant. # good # TODO(Ringo Starr): Use proper namespacing for this constant. شيفرات برمجية بدون تعليقات لا تترك أبدًا شيفراتك البرمجية بدون تعليقات. الدوال تعريف الدوال استخدم الكلمة المفتاحية def مع الأقواس عند وجود وسطاء، واحذف الأقواس عندما لا تقبل الدوالّ أي وسطاء. def some_method # body omitted end def some_method_with_parameters(arg1, arg2) # body omitted end لا تستخدم الوسطاء الموضوعة افتراضيًا، بل استخدم معاملات الكلمات الرئيسية (keyword) – الموجودة في إصدار لغة روبي Ruby 2.0 أو الأحدث - أو يمكنك استخدام أسماء hash أيضًا. # bad def obliterate(things, gently = true, except = [], at = Time.now) ... end # good def obliterate(things, gently: true, except: [], at: Time.now) ... end # good def obliterate(things, options = {}) options = { :gently => true, # obliterate with soft-delete :except => [], # skip obliterating these things :at => Time.now, # don't obliterate them until later }.merge(options) ... end تجنب الدوالّ المتكونة من سطر واحد، على الرغم من انتشارها، إلا أنه توجد بعض الغرابة حول طريقة صياغتها مما يجعل استخدامها أمرًا غير مرغوب به. # bad def too_much; something; something_else; end # good def some_method # body end دوال الاستدعاء استخدم الأقواس لدالة الاستدعاء في الحالات التالية: إذا كانت الدالة ترجع قيمة. # bad @current_user = User.find_by_id 1964192 # good @current_user = User.find_by_id(1964192) إذا كان الوسيط الأول للدالّة يستخدم الأقواس. # bad put! (x + y) % len, value # good put!((x + y) % len, value) لا تضع أبدًا مسافة بين اسم الدالّة والقوس الأول. # bad f (3 + 2) + 1 # good f(3 + 2) + 1 تجنب الأقواس عند استدعاء الدالّة إذا لم تقبل الدالّة أي وسطاء. # bad nil?() # good nil? ستكون الأقواس اختيارية ما لم تكن الدالة تُعيدّ قيمة (أو لا نهتم بما تُعيده)، أما إذا كان الوسطاء في عدة أسطر، فيمكن للأقواس أن تزيد من قابلية القراءة. # okay render(:partial => 'foo') # okay render :partial => 'foo' في كِلا الحالتين: في حالة قبول الدالة hash كوسيط أخير اختياري، لا تستخدم الأقواس { أو } أثناء الاستدعاء. # bad get '/v1/reservations', { :id => 54875 } # good get '/v1/reservations', :id => 54875 التعابير الشرطيّة الكلمات المفتاحية للجمل الشرطية لا تستخدم الكلمة المفتاحية then للجملة الشرطية if/unless المتعددة الأسطر. # bad if some_condition then ... end # good if some_condition ... end لا تستخدم الكلمة المفتاحية do مع while أو until المتعددات الأسطر. # bad while x > 5 do ... end until x > 5 do ... end # good while x > 5 ... end until x > 5 ... end إن الكلمات المفتاحية and وor وnot محظورة، فهي لا تستحق العناء، استخدم دائمًا && و|| و! بدلًا منها. يمكنك استخدام المعدِل if/unless عندما يكون الجسم بسيط والشرط بسيط وكلّ شيء في سطر واحد، وخلافًا لذلك، تجنب استخدام if/unless. # bad - this doesn't fit on one line add_trebuchet_experiments_on_page(request_opts[:trebuchet_experiments_on_page]) if request_opts[:trebuchet_experiments_on_page] && !request_opts[:trebuchet_experiments_on_page].empty? # okay if request_opts[:trebuchet_experiments_on_page] && !request_opts[:trebuchet_experiments_on_page].empty? add_trebuchet_experiments_on_page(request_opts[:trebuchet_experiments_on_page]) end # bad - this is complex and deserves multiple lines and a comment parts[i] = part.to_i(INTEGER_BASE) if !part.nil? && [0, 2, 3].include?(i) # okay return if reconciled? لا تستخدم أبدًا الكلمة المفتاحية unless مع else، أعد كتابة الشرط لتصبح الحالة الصحيحة الأولى أولًا. # bad unless success? puts 'failure' else puts 'success' end # good if success? puts 'success' else puts 'failure' end تجنب unless مع الشروط المتعددة. # bad unless foo? && bar? ... end # okay if !(foo? && bar?) ... end تجنب استخدام unless مع معاملات الموازنة فإن استطعت استخدام الشرط if مع عكس الشرط الموجود في unless ففعل ذلك. # bad unless x == 10 ... end # good if x != 10 ... end # bad unless x < 10 ... end # good if x >= 10 ... end # ok unless x === 10 ... end لا تستخدم أقواسًا حول الشروط if/unless/while. # bad if (x > 10) ... end # good if x > 10 ... end المعامل الثلاثي تجنب استخدام العامل الثلاثي (:?) إلا في الحالات الّتي تكون فيها جميع التعابير بسيطة، إلا أنه يمكنك استخدامه بدلًا من if/then/else/end في الشروط المتكونة من سطر واحد. # bad result = if some_condition then something else something_else end # good result = some_condition ? something : something_else استخدم تعبير واحد لكلّ فرع في المعامل الثلاثي، كذلك يجب ألا يكون المعامل الثلاثي متشعبًا، إذ يُفضّل استخدام بنية if/else في هذه الحالات. # bad some_condition ? (nested_condition ? nested_something : nested_something_else) : something_else # good if some_condition nested_condition ? nested_something : nested_something_else else something_else end تجنب الشروط المتعدّدة في المعامل الثلاثي إذ يُفضّل استخدام الأخير مع الشروط الفردية. تجنب الأسطر المتعدّدة :? في المعامل الثلاثي، واستخدم if/then/else/end بدلا منه. # bad some_really_long_condition_that_might_make_you_want_to_split_lines ? something : something_else # good if some_really_long_condition_that_might_make_you_want_to_split_lines something else something_else end الشروط المتشعّبة تجنب استخدام الشروط المتشعّبة لزيادة التحكم بالشيفرة (يمكنك الاطلاع على هذا المقال لمزيد من المعلومات). يُفضّل استخدام تعبير الحراسة (guard clause) عندما تريد التحقق بيانات غير صالحة، فشرط الحراسة هو عبارة شرطية في أعلى الدالة التي تعيد النتيجة بأسرع ما يمكن. المبادئ العامة هي: أعدّ القيمة (النتيجة) مباشرة بمجرد معرفتك بأن الدالّة لا طائل منها، أو أنها لا تضف أي زيادة حقيقة للشيفرة. قلل التشعّب والمسافات البادئة في الشيفرة البرمجة من خلال إعادة النتيجة بأسرع وقت ممكن. وهذا يجعل الشيفرات البرمجية أسهل للقراءة، وتتطلّب جهدً عقليًا أقل أثناء قراءة الفرع الثاني من الشرط else. ينبغي أن تكون الشروط الجوهرية أو الأكثر أهمية ذات مسافات بادئة أقل. # bad def compute server = find_server if server client = server.client if client request = client.make_request if request process_request(request) end end end end # good def compute server = find_server return unless server client = server.client return unless client request = client.make_request return unless request process_request(request) end يُفضّل استخدام next في الحلقات بدلًا من الكتل الشرطيّة. # bad [0, 1, 2, 3].each do |item| if item > 1 puts item end end # good [0, 1, 2, 3].each do |item| next unless item > 1 puts item end الصياغة لا تستخدم for إلّا إذا كنت تعرّف تمامًا لماذا ستستخدمها، فغالبًا، يجب استخدام المكرّرات (iterator) بدلًا منها، فتعمل for بنفس شروط each (أي أنك ستضيف مستوى جديد من المراوغة للبرنامج) لكن مع عيب صغير، فلا تُعرّف for نطاق جديد (على عكس each) وستظهر المتغيرات المعرّفة في كتلتها خارجيًا. arr = [1, 2, 3] # bad for elem in arr do puts elem end # good arr.each { |elem| puts elem } يفضل استخدام {...} بدلًا من do...end للكتل المكوّنة من سطر واحد، وتجنّب استخدام {...} للكتل المكونة من عدة أسطر (فسَلّسَلةُ عدة أسطر هو شيء قبيح)، واستخدم do...end دائمًا للتحكم في التدفّق ولتعريف الدوالّ، وتجنب استخدام do...end عند السَلّسَلة. names = ["Bozhidar", "Steve", "Sarah"] # good names.each { |name| puts name } # bad names.each do |name| puts name end # good names.each do |name| puts name puts 'yay!' end # bad names.each { |name| puts name puts 'yay!' } # good names.select { |name| name.start_with?("S") }.map { |name| name.upcase } # bad names.select do |name| name.start_with?("S") end.map { |name| name.upcase } سيُجادلنا البعض ويقول أن سَلّسَلَة الأسطر المتعددة تبدو جيّدة عند استخدام {...}، لكن هل سألوا أنفسهم هل هذه الشيفرة البرمجية قابلة للقراءة حقًا. وهل يمكن استخلاص محتوى الكتلة لوضعها في دوالّ فعّالة. استخدم معاملات الإسناد المختصرة كلما كان ذلك ممكنًا. # bad x = x + y x = x * y x = x**y x = x / y x = x || y x = x && y # good x += y x *= y x **= y x /= y x ||= y x &&= y تجنب الفواصل المنقوطة باستثناء حالة تعريفك لصنف ذو سطر واحد، وعندما يكون من الملائم استخدام فاصلة منقوطة، ينبغي أن تكون متاخمة مباشرةً للعبارة التي ستنهيها. لا يجب ترك مسافة قبل الفاصلة المنقوطة. # bad puts 'foobar'; # superfluous semicolon puts 'foo'; puts 'bar' # two expressions on the same line # good puts 'foobar' puts 'foo' puts 'bar' puts 'foo', 'bar' # this applies to puts in particular استخدم :: للثوابت المرجعيّة فقط (ويشمل هذا الأصناف والوحدات) والبواني (مثل: Array() أو Nokogiri::HTML()) ولا تستخدم :: لاستدعاء الدالّة العادية. # bad SomeClass::some_method some_object::some_method # good SomeClass.some_method some_object.some_method SomeModule::SomeClass::SOME_CONST SomeModule::SomeClass() تجنب استخدام return عندما تكون غير مطلوبة. # bad def some_method(some_arr) return some_arr.size end # good def some_method(some_arr) some_arr.size end لا تستخدم القيمة المُعادة من المساواة = في الجمل الشرطيّة. # bad - shows intended use of assignment if (v = array.grep(/foo/)) ... end # bad if v = array.grep(/foo/) ... end # good v = array.grep(/foo/) if v ... end استخدم ||= كما تريد لتهيئة المتغيّرات. # set name to Bozhidar, only if it's nil or false name ||= 'Bozhidar' لا تستخدم ||= لتهيئة المتغيرات المنطقيّة، (فكر في ما سيحدث إذا كانت القيمة الحالية تساوي false). # bad - would set enabled to true even if it was false enabled ||= true # good enabled = true if enabled.nil? استخدم .call الصريحة عند استدعاء lambda. # bad lambda.(x, y) # good lambda.call(x, y) تجنب استخدام متغيرات الخاصة على نمط بيرل Perl (مثل: $ أو $0-9 …إلخ)، فهي مبهمة فعلًا. واستخدامها سيُعيقنا بكلّ شيئ عدا في السكربتات ذات السطر الواحد. يُفضّل اصدارات ذات الشكل الطويل مثل: $PROGRAM_NAME. استخدم الاختصار &: عندما تأخذ كتلة الدالّة وسيط واحد فقط، ويعمل الجسم على قراءة سِمة أو استدعاء دالّة بدون وسطاء . # bad bluths.map { |bluth| bluth.occupation } bluths.select { |bluth| bluth.blue_self? } # good bluths.map(&:occupation) bluths.select(&:blue_self?) يفضلُ استخدام some_method على self.some_method عند استدعاء الدالّة داخليًا. # bad def end_date self.start_date + self.nights end # good def end_date start_date + nights end يجب عليك استخدام self. في الحالات الثلاثة التالية: عند تعريف دالّة صنف: def self.some_method. عندما تكون self هي نموذج سجل فعالّ ActiveRecord ويكون الجانب الأيسر هو استدعاء دالّة اِسناد بما في ذلك اِسناد سِمة: self.guest = user. الإشارة للصنف داخليًا: self.class. عند تعريف كائن من أي نوع قابل للتحويل وتريده أن يكون ثابتًا، تأكد من استدعاء freeze عليه، ومن الأمثلة الشائعة على ذلك هي السلاسل النصية، والمصفوفات، ومتغيرات من نوع hash (انظر لهذا المقال لمزيد من المعلومات). والسبب في ذلك أن الثوابت في لفة روبي (Ruby) هي أنواع قابلة للتحويل، وبعضها الآخر ليس كذلك. ستتأكد من عدم قابليتها للتحويل عند استدعاء freeze عليها لأنه سيصدر استثناء عند محاولة تعديلهم إن كانت غير قابلة للتعديل، وبالنسبة للسلاسل النصية، سيُسمح بالتعامل معهم لنسخ روبي ذات الاصدار الأقدم من 2.2. # bad class Color RED = 'red' BLUE = 'blue' GREEN = 'green' ALL_COLORS = [ RED, BLUE, GREEN, ] COLOR_TO_RGB = { RED => 0xFF0000, BLUE => 0x0000FF, GREEN => 0x00FF00, } end # good class Color RED = 'red'.freeze BLUE = 'blue'.freeze GREEN = 'green'.freeze ALL_COLORS = [ RED, BLUE, GREEN, ].freeze COLOR_TO_RGB = { RED => 0xFF0000, BLUE => 0x0000FF, GREEN => 0x00FF00, }.freeze end قواعد التسمية استخدم أسلوب التسمية snake_case (جميع حروف اسم المتغير صغيرة) للدوالّ والمتغيرات. استخدم أسلوب التسمية camelCase (تكبير أول حرف من كلّ كلمة في اسم المتغير عدا أول كلمة) للأصناف والوحدات. (احتفظ بالاختصارات مثل" HTTP وRFC وXML بحالة حروف كبيرة). استخدم أسلوب التسمية SCREAMING_SNAKE_CASE (جميع حروف اسم المتغير كبيرة) للثوابت الأخرى. يجب أن تنتهي الدوالّ الإسنادية (الّتي ترجع قيمة منطقية) بعلامة استفهام. (هكذا: Array#empty?). يجب أن تنتهي أسماء الدوالّ التي يُحتمل أن تكون خطرة (مثل الدوالّ الّتي تُعدلّ self أو الوسطاء، أو exit! …إلخ) بعلامة تعجب، ويجب أن تتواجد الدوالّ الخطرة (bang) إذا تتواجدت الدوالّ غير الخطرة (non-bang) معها (يمكنك الاطلاع على المقال للمزيد من المعلومات). سمِّ متغيّرات رمي الأخطاء _. version = '3.2.1' major_version, minor_version, _ = version.split('.') الأصناف تجنب استخدام متغيرات (@@) بسبب سلوكهم السيئ في الوراثة. class Parent @@class_var = 'parent' def self.print_class_var puts @@class_var end end class Child < Parent @@class_var = 'child' end Parent.print_class_var # => will print "child" كما ترى أن جميع الأصناف في التسلسل الهرمي للصنف تتشارك في متغير صنف واحد، ويجب أن تُفضّل متغيّرات مثيل الصنف على متغيّرات الصنف. استخدم def self.method لتعريف الدوالّ المتفرّدة (Singleton Methods)، وسيجعل هذا الدوالّ أكثر مقاومة لتغييرات إعادة الهيكلة. class TestClass # bad def TestClass.some_method ... end # good def self.some_other_method ... end تجنب استخدام class << self إلا عند الضرورة، فمثلًا، الجالبات المفردة (single accessors) والسمات مستعارة (aliased attributes). class TestClass # bad class << self def first_method ... end def second_method_etc ... end end # good class << self attr_accessor :per_page alias_method :nwo, :find_by_name_with_owner end def self.first_method ... end def self.second_method_etc ... end end ضع مسافة بادئة لدوالّ public وprotected وprivate بقدر تعريف الدالّة الّتي ينتمون إليها، واترك سطرًا فارغًا أعلاها وأسفلها. class SomeClass def public_method # ... end private def private_method # ... end end الاستثناءات لا تستخدم الاستثناءات للتحكم بسير البرنامج. # bad begin n / d rescue ZeroDivisionError puts "Cannot divide by 0!" end # good if d.zero? puts "Cannot divide by 0!" else n / d end تجنب إنقاذ (rescue) صنف الاستثناء Exception. # bad begin # an exception occurs here rescue Exception # exception handling end # good begin # an exception occurs here rescue StandardError # exception handling end # acceptable begin # an exception occurs here rescue # exception handling end لا تحدّد في استثناء RuntimeError وسيطين فقط في الكلمة المفتاحية raise، ومن الأفضل استخدام خطأ الأصناف الفرعية لتوضيح الخطأ وشرحه بطريقة أفضل. # bad raise RuntimeError, 'message' # better - RuntimeError is implicit here raise 'message' # best class MyExplicitError < RuntimeError; end raise MyExplicitError يُفضّل توفير صنف استثناء والرسالة كوسيطين في الكلمة المفتاحية raise بدلًا من نسخة استثناء عادي. # bad raise SomeException.new('message') # Note that there is no way to do `raise SomeException.new('message'), backtrace`. # good raise SomeException, 'message' # Consistent with `raise SomeException, 'message', backtrace`. تجنب استخدام rescue في شكل مُعدّل (Modifier). # bad read_file rescue handle_error($!) # good begin read_file rescue Errno:ENOENT => ex handle_error(ex) end التجميعات (Collections) يفضّل استخدام الخارطة (أو الخريطة) map بدلًا من تجميعة collect. يفضّل استخدام detect على find، فاستخدام find غامض بسبب دالّة find الخاصة بكائن السجل الغعال (ActiveRecord) وسيُظهر detect على أنك تعمل مع تجميعة في لغة روبي وليس كائن سجل فعال. يُفضّل استخدام reduce على inject. يُفضّل استخدام size على length وcount لتحسين أداء الشيفرة. يفّضل استخدام تدوين الإنشاء (creation notation) ومصفوفة مجزئة مصنّفة النوع (Literal)، إلّا إذا كنت بحاجة لتمرير الوسطاء لمنشئيها. # bad arr = Array.new hash = Hash.new # good arr = [] hash = {} # good because constructor requires parameters x = Hash.new { |h, k| h[k] = {} } يُفضّل استخدام Array#join بدلًا من Array#* لوضوح الشيفرة. # bad %w(one two three) * ', ' # => 'one, two, three' # good %w(one two three).join(', ') # => 'one, two, three' استخدم الرموز (symbols) بدلًا من السلاسل النصية كمفاتيح لجدول Hash. # bad hash = { 'one' => 1, 'two' => 2, 'three' => 3 } # good hash = { :one => 1, :two => 2, :three => 3 } على نحو مماثل، استخدم رموز واضحة بدلًا من رموز السلاسل النصية عندما يكون ذلك ممكنًا. # bad :"symbol" # good :symbol استخدم Hash#key? بدلًا من Hash#has_key? وHash#value? بدلًا من Hash#has_value?. فوفقًا لماتز (Matz)، تعدّ الأشكال الطويلة مُهملة. # bad hash.has_key?(:test) hash.has_value?(value) # good hash.key?(:test) hash.value?(value) استخدم جدول hash متعدّد الأسطر لأنه يجعل الشيفرة أكثر قابليّة للقراءة، واستخدم الفواصل التذييلية للتأكد من أن أي تغيّر للوسطاء لن يتسبب بتغيّرات غريبة لشكل هذه الأسطر، وذلك عندما لا يتغيّر منطق الحلّ. hash = { :protocol => 'https', :only_path => false, :controller => :users, :action => :set_password, :redirect => @redirect_url, :secret => @secret, } استخدم الفاصلة التذيليّة في المصفوفة الّتي تمتد لأكثر من سطر واحد. # good array = [1, 2, 3] # good array = [ "car", "bear", "plane", "zoo", ] السلاسل النصية يُفضّل توليد السلسلة النصية (string interpolation) بدلًا من دمج السِلاسل النصية: # bad email_with_name = user.name + ' <' + user.email + '>' # good email_with_name = "#{user.name} <#{user.email}>" وعلاوة على ذلك، لا تنس توليد نمط روبي 1.9، لنفترض أنك تُنشئ مفاتيح ذاكرة التخزين المؤقت كالتالي: CACHE_KEY = '_store' cache.write(@user.id + CACHE_KEY) مرةً أخرى يُفضّل توليد السلسلة النصية (string interpolation) بدلًا من دمج السِلاسل النصية: CACHE_KEY = '%d_store' cache.write(CACHE_KEY % @user.id) تجنب استخدام String#+ عندما تحتاج لإنشاء قطع بيانات كبيرة، واستخدم بدلًا من ذلك String#<<. تحور عملية دمج نسخ السلاسل النصية في مكانها، وهي أسرع دومًا من String#+، والّذي ينشئ مجموعة جديدة من كائنات السلسلة النصية. # good and also fast story = '' story << 'The Ugly Duckling' paragraphs.each do |paragraph| story << paragraph end استخدم \ في نهاية السطر بدلًا من + أو << لدمج السلاسل النصية متعددة الأسطر. # bad "Some string is really long and " + "spans multiple lines." "Some string is really long and " << "spans multiple lines." # good "Some string is really long and " \ "spans multiple lines." التعابير النمطية تجنب استخدام $1-9 لأنه من الصعب متابعة ما يحتويه، واستخدم بدلًا منه المجموعات المسماة (Named groups). # bad /(regexp)/ =~ string ... process $1 # good /(?<meaningful_var>regexp)/ =~ string ... process meaningful_var كن حذرًا عند استخدام ^ و$ لأنها تطابق بداية/نهاية السطر، وليس نهايات السلسلة النصية، فإذا أردت مطابقة كامل السلسلة النصية فاستخدم: \A و\z. string = "some injection\nusername" string[/^username$/] # matches string[/\Ausername\z/] # don't match استخدم المُعدّل x للتعابير النمطية المعقدة، فهذا سيجعلها أكثر قابلية للقراءة، ويمكنك إضافة بعض التعليقات المفيدة، فقط كن حذرًا لأنه سيتجاهل المسافات. regexp = %r{ start # some text \s # white space char (group) # first group (?:alt1|alt2) # some alternation end }x محارف النسبة المئوية يفضل استخدام الأقواس الهلالية على الأقواس المعقوصة، أو الأقواس المعقوفة، أو حتى الأنابيب (pipes) عند استخدام محددات مجردة % للتناسق ولأن سلوك محارف % أقرب إلى استدعاء الدالّة منه إلى البدائل. # bad %w[date locale] %w{date locale} %w|date locale| # good %w(date locale) استخدم %w كما يحلو لك. STATES = %w(draft open closed) استخدم ()% للسلاسل النصية المتكونة من سطر واحد، والّتي تتطلّب عملية توليد سلسلة وعلامات الاقتباس المزدوجة المضمّنة، وبالنسبة للسلاسل النصية المتكونة من عدة أسطر، يُفضل استخدام heredocs. # bad - no interpolation needed %(Welcome, Jane!) # should be 'Welcome, Jane!' # bad - no double-quotes %(This is #{quality} style) # should be "This is #{quality} style" # bad - multiple lines %(Welcome, Jane!\nPlease enjoy your stay at #{location}\nCheers!) # should be a heredoc. # good - requires interpolation, has quotes, single line %(Welcome, #{name}!) استخدم ٪r للتعابير النمطية فقط والّتي تتطابق مع أكثر من محرف /. # bad %r(\s+) # still bad %r(^/(.*)$) # should be /^\/(.*)$/ # good %r(^/blog/2011/(.*)$) تجنب استخدام %x إلا إذا أردت استدعاء أمر مع علامة الاقتباس الخلفية (`) - وهو أمر مستبعد إلى حد ما -. # bad date = %x(date) # good date = `date` echo = %x(echo `date`) ريلز عند العودة فورًا بعد استدعاء render أو redirect_to، ضع الكلمة المفتاحية return في السطر التالي وليس في نفس السطر. # bad render :text => 'Howdy' and return # good render :text => 'Howdy' return # still bad render :text => 'Howdy' and return if foo.present? # good if foo.present? render :text => 'Howdy' return end نطاقات عند تعريف نطاقات نموذج السجل الفعّال (ActiveRecord)، أحِط العلاقة بـ lambda، وخلافًا لذلك سيفرض الاتصال المجردة لقاعدة البيانات تنفيذه في وقت تحميل الصنف (مشابهة لطريقة بدء التشغيل). # bad scope :foo, where(:bar => 1) # good scope :foo, -> { where(:bar => 1) } انسجم مع الشيفرة إذا كنت تعدل شيفرة برمجية ما، فألقِ نظرة عليها لعدة دقائق، وحدّد أسلوب تنسيقها، فإن كانوا يستخدمون مسافات حول جميع المعاملات الرياضية، فانسجم مع هذه الطريقة، وإن كان للتعليقات صناديق صغيرة من المربعات hash حولها، فانسجم معها أيضًا. الهدف من امتلاك مبادئ توجيهية لتنسيق الشيفرة هي الحصول على مفردات مشتركة من الشيفرات ليتمكن الناس من فهم ما تقوله بدلًا من كيف تقوله، واستعرضنا هنا قواعد التنسيق العالمية حتى يعرف الناس المفردات، لكن التنسيقات شيفراتك المحلية مهمة أيضًا، فإذا كانت الشيفرة البرمجية الّتي ستضيفها تختلف اختلافًا كبيرًا عن الشيفرات البرمجية الّتي حولها فعندها ستجعل قراءة الشيفرة صعبة ولذلك تجنب هذا الأمر - دليل Google لتنسيق الشيفرة البرمجية للغة C++ ترجمة لدليل Ruby Style Guide من شركة Airbnb على موقع GitHub
-
القواعد الأساسية لا تضع إلا مكوِّن React واحدًا فقط في كل ملف. مع ذلك، يُسمح بوضع أكثر من مكوّن من الدوال عديمة الحالة (Stateless functions) في ملف واحد. استعن بقاعدة react/no-multi-comp في ESLint. استخدم دائمًا أسلوب الصياغة JSX. لا تستخدم التابع React.createElement إلا إذا كنت تهيئ التطبيق من ملف لا يستخدم صياغة JSX. Class مقابل React.createClass مقابل stateless استخدم class extends React.Component بدلًا من React.createClass إذا كانت لديك حالة أو مراجع (Refs) داخلية أو هما معًا. // سيّئ const Listing = React.createClass({ // ... render() { return <div>{this.state.hello}</div>; } }); // جيّد class Listing extends React.Component { // ... render() { return <div>{this.state.hello}</div>; } } أما إذا لم تكن لديك حالة داخلية أو مرجعية، فمن الأفضل استخدام الدوال العادية (وليس الدوال السهمية) بدلًا من استخدام الأصناف. // سيّئ class Listing extends React.Component { render() { return <div>{this.props.hello}</div>; } } // سيّئ (يُنصَح بعدم الاعتماد على استنباط اسم الدالة) const Listing = ({ hello }) => ( <div>{hello}</div> ); // جيّد function Listing({ hello }) { return <div>{hello}</div>; } الخلائط (Mixins) تجنب استخدام الخلائط. لماذا؟ لأنها تنطوي على اعتمادات (Dependencies) ضمنية، كما قد تتسبب في اشتباك الأسماء، وترفع درجة التعقيد. يمكن استبدال الخلائط في معظم الحالات بطرق أفضل عبر المكوّنات (Components)، أو المكوّنات ذات المستوى العالي (Higher-order components) أو الوحدات المساعدة. التسمية الامتدادات: استخدم الامتداد jsx. لمكوّنات React . اسم الملف: استخدم أسلوب التسمية PascalCase لأسماء الملفات. على سبيل المثال، ReservationCard.jsx. تسمية المراجع: استخدم أسلوب التسمية PascalCase لمكوّنات React وأسلوب camelCase لنسخ الكائنات (Instances). // سيّئ import reservationCard from './ReservationCard'; // جيّد import ReservationCard from './ReservationCard'; // سيّئ const ReservationItem = <ReservationCard />; // جيّد const reservationItem = <ReservationCard />; تسمية المكوّنات: استخدم اسم الملف كاسم للمكوّن. على سبيل المثال، ReservationCard.jsx ينبغي أن يكون اسم مرجعها ReservationCard. بالنسبة للمكوّنات الجذرية للمجلد (Root components)، استخدم index.jsx لاسم للملف واستخدم اسم المجلد لاسم المكوّن: // سيّئ import Footer from './Footer/Footer'; // سيّئ import Footer from './Footer/index'; // جيّد import Footer from './Footer'; تسمية المكوّنات ذات المستوى العالي (Higher-order Componen): استخدم مزيجًا من اسم المكوّن ذي المستوى العالي واسم المكوِّن المُمرَّر ليكون قيمة الخاصيّة displayName في المكوّن المُولَّد.على سبيل المثال، إذا مُرّر للمكوّن ذي المستوى العالي ()withFoo المكوّن Bar فإن الناتج ستكون قيمة الخاصيّة displayName لديه هي withFoo(Bar). لماذا ا؟ يمكن استخدام الخاصيّةdisplayName في أدوات المطوّرين أو في رسائل الخطأ، وعندما تعكس قيمتها تلك العلاقة بوضوح، فسيساعد ذلك على فهم ما يحدث. // سيّئ export default function withFoo(WrappedComponent) { return function WithFoo(props) { return <WrappedComponent {...props} foo />; } } // جيّد export default function withFoo(WrappedComponent) { function WithFoo(props) { return <WrappedComponent {...props} foo />; } const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; WithFoo.displayName = `withFoo(${wrappedComponentName})`; return WithFoo; } تسمية الخاصيّات (Props) : تجنب استعمال أسماء مكوّنات DOM لأغراض مختلفة. لماذا؟ يتوقع المطوّرون أنّ خاصيّات مثل style وclassName تعني أشياء محددة. تغيير الواجهة البرمجية هذه في جزء من تطبيقك يجعل الشفرة البرمجية أقل قابلية للقراءة والصيانة، ويمكن أن يتسبب في أعطاب. // سيّئ <MyComponent style="fancy" /> // جيّد <MyComponent className="fancy" /> // جيّد <MyComponent variant="fancy" /> التصريح Declaration لا تستخدم displayName لتسمية المكوّنات. بدلًا من ذلك، سمّ المكوّنات بمراجعها. // سيّئ export default React.createClass({ displayName: 'ReservationCard', // stuff goes here }); // جيّد export default class ReservationCard extends React.Component { } المحاذاة Alignment اتبع الأساليب التالية للمحاذاة في صياغة JSX. استعن بقاعدتيْ react/jsx-closing-bracket-location وreact/jsx-closing-tag-location في ESLint. // سيّئ <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // جيّد <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // if props fit in one line then keep it on the same line <Foo bar="bar" /> // children get indented normally <Foo superLongParam="bar" anotherSuperLongParam="baz" > <Quux /> </Foo> الاقتباس Quotes استخدم دائمًا علامات الاقتباس المزدوجة (") لخاصيّات JSX، وعلامات الاقتباس المفردة (') لبقية عناصر جافاسكريبت. استعن بقاعدة jsx-quotes في ESLint. لماذا؟ تستخدم خاصيّات HTML عادةً علامات الاقتباس المزدوجة بدلًا من المفردة، لذا فخاصيّات JSX تتبع هذا الاصطلاح. // سيّئ <Foo bar='bar' /> // جيّد <Foo bar="bar" /> // سيّئ <Foo style={{ left: "20px" }} /> // جيّد <Foo style={{ left: '20px' }} /> إدراج المسافات Spacing أضف دائمًا مسافةً واحدةً في الوسوم المنغلقة على ذاتها (Self-closing tags). استعن بالقاعدتين no-multi-spaces وreact/jsx-tag-spacing. // سيّئ <Foo/> // سيّئ جدًّا <Foo /> // سيّئ <Foo /> // جيّد <Foo /> لا تحش أقواس JSX المعقوصة بمسافات. استعن بالقاعدة react/jsx-curly-spacing. // سيّئ <Foo bar={ baz } /> // جيّد <Foo bar={baz} /> الخاصيات Props استخدم دائمًا أسلوب التسمية camelCase لتسمية الخاصيّات. // سيّئ <Foo UserName="hello" phone_number={12345678} /> // جيّد <Foo userName="hello" phoneNumber={12345678} /> احذف قيمة الخاصيّة عندما تكون قيمتها تساوي true على نحو صريح. استعن بالقاعدة react/jsx-boolean-value. // سيّئ <Foo hidden={true} /> // جيّد <Foo hidden /> // جيّد <Foo hidden /> أضف دومًا الخاصيّة alt في وسوم الصور(<img>). إذا كانت الصورة تقديمية (Presentational)، فيمكن للخاصيّة alt أن تكون نصًّا فارغًا وإلّا فيجب أن يحتوي الوسم <img> على الخاصيّة role="presentation". // سيّئ <img src="hello.jpg" /> // جيّد <img src="hello.jpg" alt="Me waving hello" /> // جيّد <img src="hello.jpg" alt="" /> // جيّد <img src="hello.jpg" role="presentation" /> لا تستخدم كلمات مثل "image" أو "photo " أو "picture" أو "صورة" في خاصيّات alt الخاصة بوسوم <img>. استعن بالقاعدة jsx-a11y/img-redundant-alt. لماذا؟ تعلم برامج قراءة الشاشة أن <img> تعني صورة، لذلك لا توجد حاجة لإدراج هذه المعلومة في النص البديل (alt text). // سيّئ <img src="hello.jpg" alt="صورة مني وأنا أشير بيدي للترحيب" /> // جيّد <img src="hello.jpg" alt="أشير بيدي للترحيب" /> لا تستخدم إلّا أدوار ARIA الصالحة وغير المجردة. استعن بالقاعدة jsx-a11y/aria-role. // سيّئ، ليس من أدوار ARIA <div role="datepicker" /> // سيّئ، دور ARIA مجرّد <div role="range" /> // جيّد <div role="button" /> لا تستخدم الخاصيّة accesskey على العناصر. استعن بالقاعدة jsx-a11y/no-access-key. لماذا؟ تعقّد التناقضات بين اختصارات لوحة المفاتيح وأوامر لوحة المفاتيح التي يستعملها مَن يستخدمون برامج قراءة الشاشة ولوحة المفاتيح، تعقّد قابلية الوصول (Accessibility). // سيّئ <div accessKey="h" /> // جيّد <div /> تجنب استخدام فهرس مصفوفة (Array index) ليكون خاصيّة key. استخدم معرّفًا فريدًا بدلًا من ذلك. (لماذا؟). // سيّئ {todos.map((todo, index) => <Todo {...todo} key={index} /> )} // جيّد {todos.map(todo => ( <Todo {...todo} key={todo.id} /> ))} عرّف دائمًا قيمًا افتراضيّة (defaultProps) للخاصيّات غير المطلوبة. لماذا؟ تعدّ القيم الافتراضيّة وسيلة للتوثيق، وتوفيرها سيجنّب قارئ الشفرة البرمجية كثيرًا من التخمينات. علاوةً على ذلك، قد يعني تعيين القيم الافتراضية أنك تستطيع الاستغناء عن التحقق في نوع البيانات أحيانا. // سيّئ function SFC({ foo, bar, children }) { return <div>{foo}{bar}{children}</div>; } SFC.propTypes = { foo: PropTypes.number.isRequired, bar: PropTypes.string, children: PropTypes.node, }; // جيّد function SFC({ foo, bar, children }) { return <div>{foo}{bar}{children}</div>; } SFC.propTypes = { foo: PropTypes.number.isRequired, bar: PropTypes.string, children: PropTypes.node, }; SFC.defaultProps = { bar: '', children: null, }; لا تُسرف في استخدام الخاصيّات الممدَّدة (Spread props). لماذا؟ لكي تتجنب قدر الإمكان تمرير الخاصيّات التي لا داعي لها إلى المكوّنات. بالنسبة للإصدار React v15.6.1 والإصدارت الأقدم، ستتجنب كذلك تمرير سمات HTML غير صالحة إلى نموذج DOM. استثناءات: المكوّنات ذات المستوى العالي التي تغلّف الخاصيّات وترفع الخاصيّة propTypes إلى أعلى النطاق (Hoist): function HOC(WrappedComponent) { return class Proxy extends React.Component { Proxy.propTypes = { text: PropTypes.string, isLoading: PropTypes.bool }; render() { return <WrappedComponent {...this.props} /> } } } تمديد الكائنات بخاصيّات معروفة وواضحة يمكن أن يكون مفيدًا خصوصًا عند اختبار مكوّنات React بالتركيب forEach في Mocha. export default function Foo { const props = { text: '', isPublished: false } return (<div {...props} />); } ملحوظة: حاول تصفية الخاصيّات التي لا داعي لها، واستخدم prop-types-exact لمساعدتك على تجنب العلل. // جيّد render() { const { irrelevantProp, ...relevantProps } = this.props; return <WrappedComponent {...relevantProps} /> } // سيّئ render() { const { irrelevantProp, ...relevantProps } = this.props; return <WrappedComponent {...this.props} /> } المرجعيّات Refs استخدم دائمًا رد نداء (Callback) للمراجع (Refs). استعن بالقاعدة react/no-string-refs. // سيّئ <Foo ref="myRef" /> // جيّد <Foo ref={(ref) => { this.myRef = ref; }} /> الأقواس ضع وسوم JSX بين أقواس إذا امتدت على أكثر من سطر واحد. استعن بالقاعدة react/jsx-wrap-multilines. // سيّئ render() { return <MyComponent variant="long body" foo="bar"> <MyChild /> </MyComponent>; } // جيّد render() { return ( <MyComponent variant="long body" foo="bar"> <MyChild /> </MyComponent> ); } // جيّد، بالنسبة لسطر واحد render() { const body = <div>hello</div>; return <MyComponent>{body}</MyComponent>; } الوسوم Tags استخدم دائمًا الإغلاق الذاتي (Self-close) للوسوم التي لا أبناء لها. استعن بالقاعدة react/self-closing-comp. // سيّئ <Foo variant="stuff"></Foo> // جيّد <Foo variant="stuff" /> اجعل إغلاق الوسوم في سطر جديد إذا كان للمكوّن خاصيّات متعددة الأسطر. استعن بالقاعدة react/jsx-closing-bracket-location. // سيّئ <Foo bar="bar" baz="baz" /> // جيّد <Foo bar="bar" baz="baz" /> التوابع Methods استخدم الدوال السهمية في المتغيرات المحلية. function ItemList(props) { return ( <ul> {props.items.map((item, index) => ( <Item key={item.key} onClick={() => doSomethingWith(item.name, index)} /> ))} </ul> ); } اربط معالجات الأحداث الخاصّة بتابع التصيير (Render methode) داخل المنشئ. استعن بالقاعدة react/jsx-no-bind. لماذا؟ استدعاء bind في مسار التصيير (Render path) يُنشئ دالة جديدة لكل تابع تصيير. // سيّئ class extends React.Component { onClickDiv() { // do stuff } render() { return <div onClick={this.onClickDiv.bind(this)} />; } } // جيّد class extends React.Component { constructor(props) { super(props); this.onClickDiv = this.onClickDiv.bind(this); } onClickDiv() { // do stuff } render() { return <div onClick={this.onClickDiv} />; } } لا تستخدم الشرطة السفلية (_) في بداية أسماء التوابع الداخلية لمكوّنات React . لماذا؟ تُستخدَم الشرطات السفلية في بداية أسماء المتغيّرات في لغات أخرى للدلالة على الخصوصية. ولكن، خلافًا لتلك اللغات، لا يوجد دعم أصيل للخصوصية في جافاسكريبت، فكل شيء فيها عام. إضافة شرطة سفلية في بداية أسماء الخاصيات، بغض النظرعن نواياك، لا يجعلها خاصة، ويجب أن تُعامَل كل الخاصيات (مسبوقة بالشرطة السفلية أو لا) على أنها عامة. انظر إلى النقاش حول 1024# وenter link description here للتعمّق. // سيّئ React.createClass({ _onClickSubmit() { // do stuff }, // other stuff }); // جيّد class extends React.Component { onClickSubmit() { // do stuff } // other stuff } تأكد من إرجاع قيمة في توابع التصيير. استعن بالقاعدة react/require-render-return. // سيّئ render() { (<div />); } // جيّد render() { return (<div />); } الترتيب Ordering الترتيب عند تمديد الصنف React.Component لإنشاء مكوّن React جديد (class extends React.Component): التوابع الثابتة (static) الاختيارية، constructor getChildContext componentWillMount componentDidMount componentWillReceiveProps shouldComponentUpdate componentWillUpdate componentDidUpdate componentWillUnmount معالجات الأحداث والنقر مثل onClickSubmit() أو onChangeDescription() توابع الوصول الخاصة بـ render مثل getSelectReason() أو getFooterContent() توابع render الاختيارية مثل renderNavigation() أو renderProfilePicture(). render. كيفية تعريف propTypes وdefaultProps وcontextTypes import React from 'react'; import PropTypes from 'prop-types'; const propTypes = { id: PropTypes.number.isRequired, url: PropTypes.string.isRequired, text: PropTypes.string, }; const defaultProps = { text: 'Hello World', }; class Link extends React.Component { static methodsAreOk() { return true; } render() { return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>; } } Link.propTypes = propTypes; Link.defaultProps = defaultProps; export default Link; • الترتيب الخاص بـ React.createClass. استعن بالقاعدة react/sort-comp. displayName propTypes contextTypes childContextTypes mixins statics defaultProps getDefaultProps getInitialState getChildContext componentWillMount componentDidMount componentWillReceiveProps shouldComponentUpdate componentWillUpdate componentDidUpdate componentWillUnmount معالجات الأحداث والنقر مثل onClickSubmit() أو onChangeDescription() توابع الوصول الخاصة بـ render مثل getSelectReason() أو getFooterContent() توابع render الاختيارية مثل renderNavigation() أو renderProfilePicture(). render لا تستخدم isMounted. استعن بالقاعدة react/no-is-mounted لماذا؟ استخدام isMounted غير مجدٍ (Anti-pattern)، وهو غير متوفر عند استخدام أصناف ES6، كما أنه سيُلغى رسميًّا قريبا. ترجمة - وبتصرّف - للمقال Airbnb React/JSX Style Guide.
-
ملاحظة: يفترض هذا الدليل أنك تستخدم مصرّف Babel، كما يتطلّب استخدام إعدادات babel-preset-airbnb المسبقة أو ما يماثلها. ويفترض أيضًا أنّك ثبّت ترقيعات متعدّدة (Polyfills/Shims)، عبر airbnb-browser-shims أو ما يماثلها. أنواع البيانات الأنواع البدائية (Primitives) عندما تتعامل مع نوع بدائي فأنت تعمل مباشرةً على قيمته. سلاسل المحارف string، الأعداد number، القيم المنطقية boolean، القيمة المعدومة null، القيمة غير المعرَّفة undefined، الرموز symbol، الأعداد الصحيحة الكبيرة bigint. const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9 لا يمكن ترقيع النوعين symbol وbigint بدقّة، لذا ينبغي ألّا تُستخدَم عند استهداف المتصفحات/البيئات التي لا تدعمها تلقائيّا. الأنواع المركبة (Complex) عند التعامل مع نوع مركّب فأنت تعمل على مرجعٍ لقيمته. الكائنات object، المصفوفات array، الدوال function. const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9 المراجع References استخدم const لجميع مراجعك. وتجنب استخدام var. استخدم قاعدتي prefer-const وno-const-assign في ESlint. لماذا؟ لأنّ هذا سيضمن لك ألّا تعيد تعيين مراجعك، وهو ما يمكن أن يؤدي إلى أخطاء، ويُصعّب فهم الشفرة البرمجية. // سيئ var a = 1; var b = 2; // جيّد const a = 1; const b = 2; إن كنت مضطرًّا لإعادة تعيين المراجع، استخدم let بدلاً من var. استخدم قاعدة no-var في ESlint. لماذا؟ لأن نطاق تعريف let محدود بالكتلة البرمجية (Block-scoped) وليس محدودًا داخل الدالة (Function-scoped) كما هو الحال مع var. // سيئ var count = 1; if (true) { count += 1; } // جيد، استخدم let. let count = 1; if (true) { count += 1; } تذكر أن نطاق كل من let و constمحدود بالكتلة. // لا توجد المتغيّرات المصرَّح عنها ب let وconst إلّا بداخل الكتل المُصرَّح فيها { let a = 1; const b = 1; } console.log(a); // خطأ في المرجع ReferenceError console.log(b); // خطأ في المرجع ReferenceError الكائنات Objects استخدم صياغة تصنيف النوع (Literal syntax) لإنشاء الكائنات. استخدم قاعدة no-new-object في ESLint. // سيئ const item = new Object(); // جيّد const item = {}; استخدم أسماء محسوبة للخاصيّات عند إنشاء كائنات بأسماء خاصيّات ديناميكية. لماذا؟ لأن ذلك سيسمح لك بتعريف جميع خاصيّات الكائن في مكان واحد. function getKey(k) { return `a key named ${k}`; } // سيئ const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // جيّد const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, }; استخدم أسلوب التعريف المختصر لتوابع الكائن. (قاعدة object-shorthand في ESLint). // سيئ const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // جيّد const atom = { value: 1, addValue(value) { return atom.value + value; }, }; استخدم التعريف المختصر لقيمة الخاصية (قاعدة object-shorthand في ESLint). لماذا؟ لأنه أقصر وأوضح. const lukeSkywalker = 'Luke Skywalker'; // سيئ const obj = { lukeSkywalker: lukeSkywalker, }; // جيّد const obj = { lukeSkywalker, }; اجمع الخاصيّات المُختصرة في بداية التصريح بالكائن (Object declaration). لماذا؟ لأنّ هذه الطريقة تسهّل معرفة أي الخاصيّات تستخدم الاختصار. const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // سيئ const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // جيّد const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, }; لا تضع بين علامات التنصيص إلّا الخاصيّات التي لها أسماء غير صالحة. لماذا؟ بشكل عام، لأنّه أسهل للقراءة ويُحسّن وضوح الكود، كما أنّه يسهُل استخدامه من قبل محركات الجافا سكريبت. // سيئ const سيئ = { 'foo': 3, 'bar': 4, 'data-blah': 5, }; // جيّد const جيّد = { foo: 3, bar: 4, 'data-blah': 5, }; لا تستدع توابع Object.prototype ، مثل hasOwnProperty، propertyIsEnumerable، و isPrototypeOf، لا تستدعها مباشرة. استخدم قاعدة no-prototype-builtins في ESLint. لماذا؟ لأنّ خاصيّات الكائن قد تغطّي تلك التوابع - انظر مثلًا إلى {hasOwnProperty: false} – علاوة على أن الكائن قد يكون معدومًا (Object.create(null)). // سيئ console.log(object.hasOwnProperty(key)); // جيّد console.log(Object.prototype.hasOwnProperty.call(object, key)); // أفضل const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope. /* أو */ import has from 'has'; // https://www.npmjs.com/package/has // ... console.log(has.call(object, key)); يفضل تطبيق عامل التمديد (Spread operator) على الكائن بدلًا من استخدام التابع Object.assign إن كنت تريد النسخ السطحي (Shallow-copy) للكائنات. أو يمكنك استخدام عامل الاستناد (Rest operator) للحصول على كائن جديد مع حذف خصائص معينة. // سيئ جدًّا const original = { a: 1, b: 2 }; const copy = Object.assign(original, { c: 3 }); // يتسبّب في التعديل على الكائن `original` delete copy.a; // الأمر نفسه هنا // سيئ const original = { a: 1, b: 2 }; const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 } // جيّد const original = { a: 1, b: 2 }; const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 } const { a, ...noA } = copy; // noA => { b: 2, c: 3 } المصفوفات Arrays استخدم صياغة تصنيف النوع (Literal syntax) لإنشاء المصفوفة (قاعدة no-array-constructor في ESLint). // سيئ const items = new Array(); // جيّد const items = []; استخدم Array#push بدلًا من الإسناد المباشر لإضافة عناصر إلى المصفوفة. const someStack = []; // سيئ someStack[someStack.length] = 'abracadabra'; // جيّد someStack.push('abracadabra'); استخدم تمديد المصفوفات ... (Array spreads) لنسخ المصفوفات. // سيئ const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i += 1) { itemsCopy[i] = items[i]; } // جيّد const itemsCopy = [...items]; استخدم عامل التمديد ... بدلًا من التابع Array.from لتحويل كائن مُكرَّر (Iterable) إلى مصفوفة. const foo = document.querySelectorAll('.foo'); // جيّد const nodes = Array.from(foo); // أفضل const nodes = [...foo]; استخدم التابع Array.from بدلًا من عامل التمديد ... لتحويل كائن شبيه بالمصفوفات إلى مصفوفة. const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 }; // سيئ const arr = Array.prototype.slice.call(arrLike); // جيّد const arr = Array.from(arrLike); استخدم التابع Array.from بدلًا من عامل التمديد ... لتطبيق الدالة map على الكائنات المُكرّرة، بهدف تجنّب خلق مصفوفة مؤقتة. // سيئ const baz = [...foo].map(bar); // جيّد const baz = Array.from(foo, bar); استخدم التعليمة return في رد نداء توابع المصفوفات (Method callbacks). لا ضير في حذف التعليمة return إن كان متن الدالة يتكون من تعليمة واحدة من دون آثار جانبية. استخدم قاعدة array-callback-return في ESLint. // جيّد [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // جيّد [1, 2, 3].map(x => x + 1); // سيئ -عدم وجود قيمة مُرجَعة يعني أن "acc" يصبح غير معرّف بعد عملية التكرار الأولى [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; }); // جيّد [[0, 1], [2, 3], [4, 5]].reduce((memo, item, index) => { const flatten = memo.concat(item); memo[index] = flatten; return flatten; }); // سيئ inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } else { return false; } }); // جيّد inbox.filter((msg) => { const { subject, author } = msg; if (subject === 'Mockingbird') { return author === 'Harper Lee'; } return false; }); استخدم سطرًا جديدًا بعد معقوفة الفتح وقبل معقوفة إغلاق المصفوفة إذا كان المصفوفة متعددة الأسطر. // سيئ const arr = [ [0, 1], [2, 3], [4, 5], ]; const objectInArray = [{ id: 1, }, { id: 2, }]; const numberInArray = [ 1, 2, ]; // جيّد const arr = [[0, 1], [2, 3], [4, 5]]; const objectInArray = [ { id: 1, }, { id: 2, }, ]; const numberInArray = [ 1, 2, ]; التفكيك Destructuring استخدم تفكيك الكائن عند التعامل مع عدة خاصيّات للكائن. لماذا؟ التفكيك يُعفيك من الحاجة إلى إنشاء مراجع مؤقتة لتلك الخصائص. // سيئ function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; return `${firstName} ${lastName}`; } // جيّد function getFullName(user) { const { firstName, lastName } = user; return `${firstName} ${lastName}`; } // أفضل function getFullName({ firstName, lastName }) { return `${firstName} ${lastName}`; } استخدم تفكيك المصفوفات. const arr = [1, 2, 3, 4]; // سيئ const first = arr[0]; const second = arr[1]; // جيّد const [first, second] = arr; استخدم تفكيك الكائن لأجل إرجاع أكثر من قيمة، وليس تفكيك المصفوفات. لماذا؟ يمكنك إضافة خاصيات جديدة مع الوقت وتغيير الترتيب دون مشاكل. // سيئ function processInput(input) { // then a miracle occurs return [left, right, top, bottom]; } // تحتاج إلى أخذ ترتيب البيانات المُرجَعة في الحسبان const [left, __, top] = processInput(input); // جيّد function processInput(input) { // then a miracle occurs return { left, right, top, bottom }; } // تختار البيانات التي تحتاجها فقط const { left, top } = processInput(input); السلاسل النصية Strings استخدم علامات التنصيص المفردة '' لتحديد السلاسل النصيّة (القاعدة quotes في ESLint). // سيئ const name = "Capt. Janeway"; // سيئ - يجب أن تحتوي القوالب مصنَّفة النوع على حشو (Interpolation) أو أسطر جديدة. const name = `Capt. Janeway`; // جيّد const name = 'Capt. Janeway'; ينبغي ألّا تُكتَب النصوص التي يتجاوز طولها 100 حرف على أسطر متعددة باستخدام ضمّ النصوص (Concatenation). لماذا؟ النصوص التي فيها أخطاء تكون مزعجةً وتجعل الكود أقل قابلية للبحث. // سيئ const errorMessage = 'This is a super long error that was thrown because \ of Batman. When you stop to think about how Batman had anything to do \ with this, you would get nowhere \ fast.'; // سيئ const errorMessage = 'This is a super long error that was thrown because ' + 'of Batman. When you stop to think about how Batman had anything to do ' + 'with this, you would get nowhere fast.'; // جيّد const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'; عند بناء النصوص برمجيًّا، استخدم القوالب النصيّة بدلًا من ضمّ النصوص (قاعدة prefer-template template-curly-spacing في ESLint). لماذا؟ تجعل القوالب النصيّة الشفرة أكثر مقروئية وإيجازًا، مع أسطر جديدة مناسبة وميزات حشو النصوص. // سيئ function sayHi(name) { return 'How are you, ' + name + '?'; } // سيئ function sayHi(name) { return ['How are you, ', name, '?'].join(); } // سيئ function sayHi(name) { return `How are you, ${ name }?`; } // جيّد function sayHi(name) { return `How are you, ${name}?`; } لا تطبّق أبدًا ()eval على النصوص، لأنها تتسبّب في الكثير من الثغرات. استخدم قاعدة no-eval في ESLint. تجنب تخليص الأحرف (Escape characters) في النصوص قدر الإمكان. لماذا؟ الخط المائل العكسي يضر بالمقروئية، وبالتالي يجب ألًا يُستخدم إلا عند الضرورة. // سيئ const foo = '\'this\' \i\s \"quoted\"'; // جيّد const foo = '\'this\' is "quoted"'; const foo = `my name is '${name}'`; الدوال استخدم الدوال العبارات (Function expressions) المُسماة بدلًا من التصريح بالدوال (قاعدة func-style في ESLint). لماذا؟ يضع محرَك جافاسكريبت الدوال المُصرَّح بها في بداية النطاق (تُعرَف هذه العملية بالرفع Hoist)، وبالتالي يمكن استخدام الدالة قبل تعريفها في الملف. يعني ذلك أنه سيكون من السهل الإحالة إلى الدالة قبل أن تُعرَّف في الملف، وهو ما يضر المقروئية وقابلية الصيانة. إذا وجدت أن تعريف دالة ما كبير أو معقد حدّ الإرباك، فربما حان الوقت لوضعه في وحدة خاصة به! لا تنس أن تُسمّي العبارة صراحة، بغض النظر عما إذا كان الاسم مستنتجًا من المتغير الحاوي (كما هو الحال غالبًا في المتصفحات الحديثة أو عند استخدام مصرّفات مثل Babel) أم لا. تلغي هذه الطريقة أي افتراضات حول مكدس النداء إلى الخطأ Error’s call stack // سيئ function foo() { // ... } // سيئ const foo = function () { // ... }; // جيّد // اسم العبارة الدالة مغاير لاسم الاستدعاء الذي يحيل إلى المتغيّر const short = function longUniqueMoreDescriptiveLexicalFoo() { // ... }; ضع الدوال العبارات فورية الاستدعاء (Immediately-invoked Function Expression، أو IIFE اختصارًا)، ضعها بين قوسين. استخدم قاعدة wrap-iife في ESLint. لماذا؟ الدوال العباراة فورية الاستدعاء هي وحدة منفردة، لذلك وضعها، هي وعبارة استدعائها، بين قوسين يجعل الشفرة واضحة. وإن كان من المستبعد جدّا أن تحتاج الدوال فورية الاستدعاء (IIFE) في مشاريع تكثر من استخدام الوحدات الوظيفية (Modules). // immediately-invoked function expression (IIFE) (function () { console.log('Welcome to the Internet. Please follow me.'); }()); لا تصرّح أبدًا بالدوال في كتلة غير خاصة بالدوال (مثل if، وwhile، …إلخ). أسند الدالة إلى متغير بدلًا من ذلك. تسمح المتصفحات بالتصريح بالدالة في تلك الكتل، إلّا أنها ستفسرها بطرق مختلفة، وهو أمر لا يحبه المبرمجون. استخدم قاعدة no-loop-func في ESLint. ملحوظة: يعرّف معيار ECMA-262 الكتلة (Block) بأنها قائمة من التعليمات البرمجية (Statements). والتصريح بدالة (Function decleration) ليس تعليمة // سيئ if (currentUser) { function test() { console.log('Nope.'); } } // جيّد let test; if (currentUser) { test = () => { console.log('Yup.'); }; } لا تسمّي معاملًا بالاسم arguments. لأنه سيأخذ الأسبقية على الكائن arguments الذي يُحدد تلقائيّا في نطاق كل دالة. // سيئ function foo(name, options, arguments) { // ... } // جيّد function foo(name, options, args) { // ... } لا تستخدم أبدًا المعامل arguments، واستخدم بدلًا منه عامل التمديد (...). لماذا؟ عامل التمديد واضح في تحديد الوسائط التي تريد سحبها. بالإضافة إلى ذلك، وسائط عامل التمديد هي مصفوفة حقيقة، وليست شبيهة بالمصفوفة مثل الكائن arguments. استخدم قاعدة prefer-rest-params في ESLint. // سيئ function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // جيّد function concatenateAll(...args) { return args.join(''); } استخدم الصيغة الافتراضية للمعاملات بدلًا من التعديل على وسائط الدالة. // سيئ جدّا function handleThings(opts) { // لا! يجب ألّا نعدّل على وسائط الدالة. // أمر سيئ آخر: إذا كانت قيمة الوسيط opts تساوي القيمة المنطقية false فسيُسنَد كائن إلى الوسيط opts، وهو ما قد تريده إلّا أنه يتسبب في ثغرات تصعب ملاحظتها opts = opts || {}; // ... } // سيئ أيضا function handleThings(opts) { if (opts === void 0) { opts = {}; } // ... } // جيّد function handleThings(opts = {}) { // ... } تجنب الآثار الجانبية في المعاملات الافتراضية. لماذا؟ لأنّها مربكة. var b = 1; // سيئ function count(a = b++) { console.log(a); } count(); // 1 count(); // 2 count(3); // 3 count(); // 3 ضع دائمًا المعاملات الافتراضية في الأخير. // سيئ function handleThings(opts = {}, name) { // ... } // جيّد function handleThings(name, opts = {}) { // ... } لا تستخدم أبدًا منشئ الدوال (Function constructor) لإنشاء دالة جديدة. استخدم قاعدة no-new-func في ESLint. لماذا؟ إنشاء دالة بهذه الطريقة يتضمّن تقييم نص كما تفعل ()eval، وهو ما يفتح نقاط ضعف. // سيئ var add = new Function('a', 'b', 'return a + b'); // سيئ كذلك var subtract = Function('a', 'b', 'return a - b'); ضع مسافات في توقيع الدالة. استخدم قاعدتي space-before-function-paren وspace-before-blocks في ESLint. لماذا؟ الاتساق أمر جيد، كما لن تكون مضطرًا لإضافة أو إزالة مسافة عند إضافة أو إزالة اسم. // سيئ const f = function(){}; const g = function (){}; const h = function() {}; // جيّد const x = function () {}; const y = function a() {}; لا تعدّل أبدًا على المعاملات. استخدم قاعدة no-param-reassign في ESLint. لماذا؟ يمكن للتعديل على الكائنات التي مُرُرت كمعاملات أن يتسبب في آثار جانبية غير مرغوب فيها في المستدعي الأصلي. // سيئ function f1(obj) { obj.key = 1; } // جيّد function f2(obj) { const key = Object.prototype.hasOwnProperty.call(obj, 'key') ? obj.key : 1; } لا تُعد إسناد المعاملات. استخدم قاعدة no-param-reassign في ESLint. لماذا؟ إعادة إسناد المعاملات يمكن أن يؤدي إلى سلوك غير متوقع، خصوصًا عند التعامل مع الكائن arguments. كما يمكن أن يسبب مشاكل في الأداء، خصوصا في V8. // سيئ function f1(a) { a = 1; // ... } function f2(a) { if (!a) { a = 1; } // ... } // جيّد function f3(a) { const b = a || 1; // ... } function f4(a = 1) { // ... } من الأفضل استخدام استخدام عامل التمديد (...) لاستدعاء الدوال متغيرة عدد الوسائط (Variadic functions). استخدم قاعدة prefer-spread في ESLint. لماذا؟ لأنها أوضح، فلست مضطرًّا لتجهيز السياق، كما لا يمكنك أن تجمع بسهولة new مع apply. // سيئ const x = [1, 2, 3, 4, 5]; console.log.apply(console, x); // جيّد const x = [1, 2, 3, 4, 5]; console.log(...x); // سيئ new (Function.prototype.bind.apply(Date, [null, 2016, 8, 5])); // جيّد new Date(...[2016, 8, 5]); الدوال التي لها توقيعات أو استدعاءات متعددة الأسطر، ينبغي أن تكون مسافاتها البادئة تمامًا مثل كل القوائم متعددة الأسطر الأخرى في هذا الدليل، بمعنى أن كل عنصر في سطر، مع فاصلة زائدة بعد العنصر الأخير. // سيئ function foo(bar, baz, quux) { // ... } // جيّد function foo( bar, baz, quux, ) { // ... } // سيئ console.log(foo, bar, baz); // جيّد console.log( foo, bar, baz, ); الدوال السهمية Arrow Functions استخدم صيغة الدالة السهمية عندما تكون مضطرًّا لاستخدام دالة مجهولة (Anonymous function)، مثلًا عند تمرير ردّ نداء (Callback) على السطر. استخدم قاعدتي prefer-arrow-callback وarrow-spacing في ESlint. لماذا؟ لأنها تخلق نسخة من الدالة تُنفَّذ في السياق this، وهو عادةً ما ترغب فيه، كما أنها أكثر إيجازا. متى تتخلّى عنها؟ إذا كانت لديك دالة معقدة، فيمكنك نقل وظيفتها إلى دالة عبارة مُسمّاة. // سيئ [1, 2, 3].map(function (x) { const y = x + 1; return x * y; }); // جيّد [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); إن كان متن الدالة يتكون من تعليمة واحدة تُرجِع تعبيرًا دون آثار جانبية، فاحذف القوسين المعقوصيْن ({}) واستخدم الإرجاع الضمني (بدون التعليمة return). خلافًا لذلك، أبق على الأقواس المعقوصة واستخدم التعليمة return. استعن بقاعدتي arrow-parens و arrow-body-style في ESLint. لماذا؟ تسهيل قراءة الشفرة عند استخدام دوال بالتسلسل. // سيئ [1, 2, 3].map(number => { const nextNumber = number + 1; `A string containing the ${nextNumber}.`; }); // جيّد [1, 2, 3].map(number => `A string containing the ${number}.`); // جيّد [1, 2, 3].map((number) => { const nextNumber = number + 1; return `A string containing the ${nextNumber}.`; }); // جيّد [1, 2, 3].map((number, index) => ({ [index]: number, })); // لا يوجد إرجاع ضمني لكن توجد آثار جانبية function foo(callback) { const val = callback(); if (val === true) { // افعل شيئًا هنا إذا كان رد النداء إيجابيا } } let bool = false; // سيئ foo(() => bool = true); // جيّد foo(() => { bool = true; }); في حال امتد التعبير عبر عدة أسطر، ضعه بين قوسين لمقروئية أكبر. لماذا؟ لتوضيح أين تبدأ وأين تنتهي الدالة. // سيئ ['get', 'post', 'put'].map(httpMethod => Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) ); // جيّد ['get', 'post', 'put'].map(httpMethod => ( Object.prototype.hasOwnProperty.call( httpMagicObjectWithAVeryLongName, httpMethod, ) )); أضف دائمًا أقواسًا حول الوسائط من أجل الوضوح والتناسق. استعن بقاعدة arrow-parens في ESlint. لماذا؟ تقليل الأخطاء عند إضافة وسائط أو حذفها. // سيّئ [1, 2, 3].map(x => x * x); // جيّد [1, 2, 3].map((x) => x * x); // سيّئ [1, 2, 3].map(number => ( `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` )); // جيّد [1, 2, 3].map((number) => ( `A long string with the ${number}. It’s so long that we don’t want it to take up space on the .map line!` )); // سيّئ [1, 2, 3].map(x => { const y = x + 1; return x * y; }); // جيّد [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); تجنب الخلط بين صياغة الدوال السهمية (=>) وبين عوامل المقارنة (<= , >=). استعن بقاعدة no-confusing-arrow. // سيئ const itemHeight = item => item.height > 256 ? item.largeSize : item.smallSize; // سيئ const itemHeight = (item) => item.height > 256 ? item.largeSize : item.smallSize; // جيّد const itemHeight = item => (item.height > 256 ? item.largeSize : item.smallSize); // جيّد const itemHeight = (item) => { const { height, largeSize, smallSize } = item; return height > 256 ? largeSize : smallSize; }; افرض موقع متن الدوال السهميّة عن طريق الإرجاع الضمني (Implicit return). استعن بقاعدة implicit-arrow-linebreak. // سيّئ (foo) => bar; (foo) => (bar); // جيّد (foo) => bar; (foo) => (bar); (foo) => ( bar ) الأصناف والمُنشِئات Classes & Constructors استخدم دائمًا الكلمة المفتاحية class. وتجنب التعامل مع الخاصيّة prototype مباشرة. لماذا؟ العبارة class أكثر إيجازا ووضوحا. // سيئ function Queue(contents = []) { this.queue = [...contents]; } Queue.prototype.pop = function () { const value = this.queue[0]; this.queue.splice(0, 1); return value; }; // جيّد class Queue { constructor(contents = []) { this.queue = [...contents]; } pop() { const value = this.queue[0]; this.queue.splice(0, 1); return value; } } استخدم الكلمة المفتاحية extends للتوارث بين الأصناف. لماذا؟ لأنها وسيلة مدمجة لوارثة الوظائف من النموذج الأولي دون التسبب بمشاكل عند استخدام العامل instanceof. // سيئ const inherits = require('inherits'); function PeekableQueue(contents) { Queue.apply(this, contents); } inherits(PeekableQueue, Queue); PeekableQueue.prototype.peek = function () { return this.queue[0]; }; // جيّد class PeekableQueue extends Queue { peek() { return this.queue[0]; } } يمكن للتوابع أن تُرجع الكائن this للمساعدة استخدام التوابع بالتسلسل. // سيئ Jedi.prototype.jump = function () { this.jumping = true; return true; }; Jedi.prototype.setHeight = function (height) { this.height = height; }; const luke = new Jedi(); luke.jump(); // => true luke.setHeight(20); // => undefined // جيّد class Jedi { jump() { this.jumping = true; return this; } setHeight(height) { this.height = height; return this; } } const luke = new Jedi(); luke.jump() .setHeight(20); لا بأس في تخصيص التابع ()toString، لكن تأكد من أنه يعمل بسلاسة ولا تتسبب في أي آثار جانبية. class Jedi { constructor(options = {}) { this.name = options.name || 'no name'; } getName() { return this.name; } toString() { return `Jedi - ${this.getName()}`; } } توجد منشئات افتراضية للأصناف يُلجَا إليها إنْ لم يُحدّد منشئ سلفا. لذا، فلا حاجة لمنشئات فارغة أو منشئات تقتصر على الإنابة عن الصنف الأب. استعن بقاعدة no-useless-constructor في ESLint. // سيئ class Jedi { constructor() {} getName() { return this.name; } } // سيئ class Rey extends Jedi { constructor(...args) { super(...args); } } // جيّد class Rey extends Jedi { constructor(...args) { super(...args); this.name = 'Rey'; } } تجنب تكرار عناصر الصنف. استعن بقاعدة no-dupe-class-members في ESLint. لماذا؟ في حال التصريح المكرر لعنصر من عناصر الصنف، فالقيمة الأخيرة فقط هي التي ستُعتمَد. وجود التكرار يعني بالضرورة وجود علّة في الشفرة البرمجية. // سيئ class Foo { bar() { return 1; } bar() { return 2; } } // جيّد class Foo { bar() { return 1; } } // جيّد class Foo { bar() { return 2; } } يجب أن تستخدم توابع الصنف الكائن this أو تُدرَج في تابع ثابت (Static) إلّا إذا استدعت مكتبة خارجية أو إطار عمل استخدام توابع غير ثابتة. كون التابع مرتبطًا بنظير كائن (Instance) يجب أن يشير إلى أنه يتصرف بسلوك مختلف حسب خاصيّات الكائن المستقبل. استعن بالقاعدة class-methods-use-this. // سيّئ class Foo { bar() { console.log('bar'); } } // جيّد، استخدام this class Foo { bar() { console.log(this.bar); } } // جيّد، المنشئ مستثنى من القاعدة constructor() { // ... } } // جيّد، يفترض ألا تستخدم التوابع الثابتة الكائن this class Foo { static bar() { console.log('bar'); } } الوحدات Modules استخدم دائمًا الكلمتيْن المفتاحيتيْن (import/export) لاستيراد أو تصدير الوحدات، بدلًا من الطرق الأخرى غير القياسية. يمكنك دائمًا تصريف الوحدات المفضلة لديك إلى شفرة جافاسكريبت (Transpile). لماذا؟ الوحدات هي المستقبل، دعونا نُدشن المستقبل منذ الآن. // سيئ const AirbnbStyleGuide = require('./AirbnbStyleGuide'); module.exports = AirbnbStyleGuide.es6; // مقبول import AirbnbStyleGuide from './AirbnbStyleGuide'; export default AirbnbStyleGuide.es6; // أفضل import { es6 } from './AirbnbStyleGuide'; export default es6; لا تعتمد على محارف البدل (Wild card) في استيراد الوحدات (الاستيراد بالجملة). لماذا؟ لتتأكد من أنّ لديك تصديرًا افتراضيّا واحدا. // سيئ import * as AirbnbStyleGuide from './AirbnbStyleGuide'; // جيّد import AirbnbStyleGuide from './AirbnbStyleGuide'; لا تُصدّر مباشرةً من الاستيراد. لماذا؟ على الرغم من أن الكتابة في سطر واحد تكون أوجز، إلّا أن وجود طريقة واضحة واحدة للاستيراد وأخرى للتصدير يجعل الأمور متسقة. // سيئ // filename es6.js export { es6 as default } from './AirbnbStyleGuide'; // جيّد // filename es6.js import { es6 } from './AirbnbStyleGuide'; export default es6; استورد من مسار معيّن في مكان واحد فقط. استعن بقاعدة no-duplicate-imports. لماذا؟ وجود عدة أسطر تستورد من المسار نفسه يمكن أن يجعل الشفرة أقل قابلية للصيانة. // سيئ import foo from 'foo'; // … some other imports … // import { named1, named2 } from 'foo'; // جيّد import foo, { named1, named2 } from 'foo'; // جيّد import foo, { named1, named2, } from 'foo'; لا تُصدر الارتباطات المتحوّلة (Mutable bindings). استعن بقاعدة no-duplicate-imports. لماذا؟ يُفضّل عمومًا تجنب العناصر المتحولة، ولكن على وجه الخصوص عند تصدير الارتباطات المتحولة. على الرغم من أن هذا الأسلوب قد يكون ضروريّا في حالات خاصة، إلّا أنّ القاعدة الأساسية هي ألّا تُصدرَ إلا المراجع الثابتة. // سيئ let foo = 3; export { foo }; // جيّد const foo = 3; export { foo }; في الوحدات التي فيها تصدير واحد فقط، يفضل استخدام التصدير الافتراضي بدلًا من التصدير المسمى named export. استعن بقاعدة import/prefer-default-export. لماذا؟ لتشجيع استخدام الملفات التي لا تصدر إلًا شيئًا واحدًا فقط، لأن ذلك أفضل وأسهل للقراءة والصيانة. // سيئ export function foo() {} // جيّد export default function foo() {} ضع كل عبارات import فوق عبارات الاستيراد الأخرى. استعن بقاعدة import/first في ESLint. لماذا؟ بما أن محرّك جافاسكريبت يرفع تعليمات import إلى أعلى النطاق (Hoisted)، فوضعها كلها في الجزء العلوي يمنع أي سلوك غير متوقع. // سيئ import foo from 'foo'; foo.init(); import bar from 'bar'; // جيّد import foo from 'foo'; import bar from 'bar'; foo.init(); يجب أن توضع مسافة بادئة قبل عناصر الاستيراد ذي الأسطر المتعدّدة، مثله مثل المصفوفات متعددة الأسطر والكائنات مصنّفة النوع (Object literals). استعن بالقاعدة object-curly-newline. لماذا؟ تتبع الأقواس المعقوصة قواهد المسافات البادئة ذاتها التي تتبعها كتل الأقواس المعقوصة الأخرى في هذا الدليل. الأمر نفسه ينطبق على الفواصل الزائدة (Trailing commas). // سيئ import {longNameA, longNameB, longNameC, longNameD, longNameE} from 'path'; // جيّد import { longNameA, longNameB, longNameC, longNameD, longNameE, } from 'path'; لا تسمح بصيغة Webpack للتحميل في تعليمات استيراد الوحدات. استعن بالقاعدة import/no-webpack-loader-syntax. لماذا؟ لأن استخدام صيغة Webpack في الاستيراد تجعل الشفرة معتمدة على محزّم (Bundler). من الأفضل استخدام صيغة Webpack في الملف webpack.config.js. // سيئ import fooSass from 'css!sass!foo.scss'; import barCss from 'style!css!bar.css'; // جيّد import fooSass from 'foo.scss'; import barCss from 'bar.css'; لا تُضمّن امتداد ملفات جافاسكريبت في الاستيراد. استعن بالقاعدة import/extensions. لماذا؟ لأن تضمين الامتدادات يصعّب إعادة كتابة الشفرة، ويضع لدى جميع العملاء تفاصيل غير مناسبة عن الوحدة التي تستوردها. // سيّئ import foo from './foo.js'; import bar from './bar.jsx'; import baz from './baz/index.jsx'; // جيّد import foo from './foo'; import bar from './bar'; import baz from './baz'; المُكرّرات والمولّدات لا تستخدم المكررات (Iterators). من الأفضل استخدام دوال الدرجات العليا لجافاسكريبت بدلًا من الحلقات مثلfor-in أو for-of. لماذا؟ هذا يتماشى مع قاعدة انعدام التحول (Immutable rule) التي ننتهجها. التعامل مع الدوال التي تُرجع قيمًا أسهل للفهم مقارنة بالدوال ذات الآثار الجانبية. استعن بقاعدتي no-iterator وno-restricted-syntax. استخدم الدوال ()map / every() / filter()، find() / findIndex() / reduce() / some / للمرور على المصفوفات، والتوابع ()Object.keys / Object.values() / Object.entries لإنتاج مصفوفات حتى تتمكن من المرور على عناصر الكائن. const numbers = [1, 2, 3, 4, 5]; // سيئ let sum = 0; for (let num of numbers) { sum += num; } sum === 15; // جيّد let sum = 0; numbers.forEach((num) => { sum += num; }); sum === 15; // أفضل (استخدام فعالية الدوال، برمجة وظيفية) const sum = numbers.reduce((total, num) => total + num, 0); sum === 15; // سيئ const increasedByOne = []; for (let i = 0; i < numbers.length; i++) { increasedByOne.push(numbers[i] + 1); } // جيّد const increasedByOne = []; numbers.forEach((num) => { increasedByOne.push(num + 1); }); // أفضل (الاعتماد على التصور الوظيفي) const increasedByOne = numbers.map(num => num + 1); لا تستخدم المولدات (Generators) في الوقت الراهن. لماذا؟ لأنها لا تُصرَّف على نحو جيّد إلى ES5. إذا كان عليك استخدام المُولدات، أو إذا أردت تجاهل نصيحتنا، تأكد من أن تواقيع دوالها متباعدة بشكل صحيح. استعن بالقاعدة generator-star-spacing. لماذا؟ function و * هما كلمتان مفتاحيتان من نفس المفهوم، فالكلمة المفتاحية * ليست تعديلًا للكلمة المفتاحية function، و function* مختلفة عن function. // سيئ function * foo() { // ... } // سيئ const bar = function * () { // ... }; // سيئ const baz = function *() { // ... }; // سيئ const quux = function*() { // ... }; // سيئ function*foo() { // ... } // سيئ function *foo() { // ... } // سيئ جدّا function * foo() { // ... } // سيئ جدّا const wat = function * () { // ... }; // جيّد function* foo() { // ... } // جيّد const foo = function* () { // ... }; الخصائص استخدم أسلوب الترميز بالنقطة (Dot notation) عند الدخول إلى الخصائص. const luke = { jedi: true, age: 28, }; // سيئ const isJedi = luke['jedi']; // جيّد const isJedi = luke.jedi; استخدام المعقوفتين [] عند الدخول إلى الخاصيّات بواسطة متغير. const luke = { jedi: true, age: 28, }; function getProp(prop) { return luke[prop]; } const isJedi = getProp('jedi'); استخدام العامل الأسي ** عند حساب الأسّ. // سيئ const binary = Math.pow(2, 10); // جيّد const binary = 2 ** 10; المتغيرات استخدم دومًا const أو let للتصريح بالمتغيرات. عدم القيام بذلك سيؤدي إلى إنشاء متغيرات عامة، في حين نريد تجنب تلويث فضاء الأسماء العام (Global namespace). استعن بقاعدتيْ no-undef وprefer-const. // سيئ superPower = new SuperPower(); // جيّد const superPower = new SuperPower(); صرّح باستخدام const أو let لكل متغيّر على حدة. استعن بقاعدة one-var في ESlint. لماذا؟ من الأسهل إضافة متغيرات جديدة بهذه الطريقة، ولن تكون مضطرا لإبدال الفاصلة المنقوطة ; بفاصلة , كل ما أردت إضافة متغيّر جديد، وستتخلّص من التعديلات المقتصرة على علامات التنقيط. ستتمكّن كذلك من المرور خلال التنقيح (Debugging) على كل متغيّر على حدة، بدلًا من القفز عليها كلها في وقت واحد. // سيئ const items = getItems(), goSportsTeam = true, dragonball = 'z'; // سيئ (قارن بالتصريح فوقه لتعثر على الخطأ) const items = getItems(), goSportsTeam = true; dragonball = 'z'; // جيّد const items = getItems(); const goSportsTeam = true; const dragonball = 'z'; جمّع كل التصريحات بالكلمة المفتاحية const ثم بعدها التصريحات بالكلمة المفتاحية let. لماذا؟ سيكون هذا مفيدًا لاحقًا عندما تحتاج إلى إسناد متغير اعتمادًا على متغير مُسنَد مسبقا. // سيئ let i, len, dragonball, items = getItems(), goSportsTeam = true; // سيئ let i; const items = getItems(); let dragonball; const goSportsTeam = true; let len; // جيّد const goSportsTeam = true; const items = getItems(); let dragonball; let i; let length; أسند المتغيرات حيث تحتاج لذلك، لكن ضعها في مكان مناسب. لماذا؟ المتغيّرات المصرّح عنها بـ let أو const ذات نطاق كتلي (Block scoped) وليست ذات نطاق دالّي (Function scoped). // سيئ، لا حاجة لنداء الدالة function checkName(hasName) { const name = getName(); if (hasName === 'test') { return false; } if (name === 'test') { this.setName(''); return false; } return name; } // جيّد function checkName(hasName) { if (hasName === 'test') { return false; } const name = getName(); if (name === 'test') { this.setName(''); return false; } return name; } تجنب الإسناد المتسلسل للمتغيّرات. استعن no-multi-assign بقاعدة في ESLint. لماذا؟ لأن الإسناد المتسلسل ينتج متغيرات عامة. // سيئ (function example() { // يفسّر جافاسكريبت التعليمة أدناه على النحو التالي // let a = ( b = ( c = 1 ) ); // لا يُطبّق التصريح بـ let إلا على المتغيّر a، // المتغيّران b وc يصبحان عامين. let a = b = c = 1; }()); console.log(a); // يتسبّب في خطأ في المرجع ReferenceError console.log(b); // 1 console.log(c); // 1 // جيّد (function example() { let a = 1; let b = a; let c = a; }()); console.log(a); // يتسبّب في خطأ في المرجع ReferenceError console.log(b); // يتسبّب في خطأ في المرجع ReferenceError console.log(c); // يتسبّب في خطأ في المرجع ReferenceError // الأمر نفسه ينطبق على `const` تجنب استخدام عمليات الزيادة والإنقاص الأحادية (++, --). استعن بقاعدة no-plusplus. لماذا؟ تخضع عمليات الزيادة والإنقاص الأحادية، حسب وثائق ESLint، لقاعدة الإدراج التلقائي للفواصل المنقوطة، ويمكن أن تتسبب في حدوث أخطاء صامتة مع زيادة أو إنقاص قيمة ضمن التطبيق. كما أنه من الأكثر وضوحًا استخدام num += 1 بدلًا من num++ أو num ++ لتغيير قيم المتغيّرات. تجنُّب استخدام عامل الزيادة والإنقاص الأحادي سيجنبك الزيادة (أو الإنقاص) قبل إجراء عملية، وهو ما يمكن أيضًا أن يسبب سلوكًا غير متوقع في برامجك. // سيئ const array = [1, 2, 3]; let num = 1; num++; --num; let sum = 0; let truthyCount = 0; for (let i = 0; i < array.length; i++) { let value = array[i]; sum += value; if (value) { truthyCount++; } } // جيّد const array = [1, 2, 3]; let num = 1; num += 1; num -= 1; const sum = array.reduce((a, b) => a + b, 0); const truthyCount = array.filter(Boolean).length; تجنّب إدراج الأسطر قبل علامة الإسناد = أو بعدها. أحط القيمة بقوسيْن إنْ كان الإسناد يخرق قاعدة max-len (طول السطر). استعن بقاعدة operator-linebreak. لماذا لأن الأسطر حول = يمكن أن تعتّم على قيمة الإسناد. // سيّئ const foo = superLongLongLongLongLongLongLongLongFunctionName(); // سيّئ const foo = 'superLongLongLongLongLongLongLongLongString'; // جيّد const foo = ( superLongLongLongLongLongLongLongLongFunctionName() ); // جيّد const foo = 'superLongLongLongLongLongLongLongLongString'; لا تسمح بمتغيّرات غير مستخدمة. استعن بالقاعدة no-unused-vars. لماذا؟ المتغيّرات المُصرّح بها غير المستخدمة في أي جزء من الشفرة هي في الغالب خطأ ناتج عن إعادة هيكلة غير مكتملة. تأخذ هذه المتغيّرات مساحة من الشفرة ويمكن أن تتسبّب في خلط لدى من يقرأه. // سيّئ var some_unused_var = 42; // متغيّرات يقتصر استخدامها على تغيير قيمتها لا تعد مستخدمة. var y = 10; y = 5; // قراءة المتغيّر فقط من أجل التعديل عليه لا يعدّ استخداما var z = 0; z = z + 1; // وسائط غير مستخدمة في الدالة function getX(x, y) { return x; } // جيّد function getXPlusY(x, y) { return x + y; } var x = 1; var y = a + 2; alert(getXPlusY(x, y)); // لا تنطبق القاعدة هنا على المتغيّر type الذي على الرغم من أنه غير مستخدم إلا أن له علاقة بالمتغيّر المُفكَّك // هذه طريقة لاستخراج كائن بإسقاط المفاتيح المحدّدة. var { type, ...coords } = data; // يطابق الكائنُ coords الكائنَ data، غير أنه لا توجد فيه الخاصيّة type التي استخرجناها منه. الرفع إلى أعلى النطاق Hoisting تُرفَع المتغيّرات المُصرَّح عنها بالكلمة المفتاحية var إلى أعلى نطاق الدالة المحتوية الأقرب، إلّا أن هذا الأمر لا ينطبق على عمليّات الإسناد. للمتغيّرات المُعرَّفة بالكلمتيْن المفتاحيتيْن const و let ميزة جديدة تُسمى المناطق الميتة الظرفية (Temporal dead zones)؛ لذا من المهم أن تعرف لماذا لم يعد استخدام الدالة typeof آمنا. // نعرف أن التعليمات التالية لن تعمل (على فرض // أنه لا يوجد متغيّر باسم notDefined) function example() { console.log(notDefined); // يتسبب في خطأ ReferenceError } // تستطيع التصريح بمتغيّر بعد الإحالة إليه // لأن التصريح سيُرفَع إلى أعلى النطاق // ملحوظة: إسناد القيمة true إلى المتغيّر لا يُرفَع // إلى أعلى النطاق. function example() { console.log(declaredButNotAssigned); // => undefined var declaredButNotAssigned = true; } // يرفع مفسّر جافاسكريبت التصريح بالمتغيّر // إلى أعلى النطاق، وهو ما يعني أنه بإمكاننا // إعادة كتابة المثال السابق على النحو التالي function example() { let declaredButNotAssigned; console.log(declaredButNotAssigned); // => undefined declaredButNotAssigned = true; } // استخدام const وlet function example() { console.log(declaredButNotAssigned); // تتسبّب في خطأ ReferenceError console.log(typeof declaredButNotAssigned); // تتسبّب في خطأ ReferenceError const declaredButNotAssigned = true; } يُرفَع اسم متغيّر الدوال العبارات غير المُسمّاة إلى أعلى النطاق، بخلاف إسناد الدالة. function example() { console.log(anonymous); // => undefined anonymous(); // => TypeError anonymous is not a function var anonymous = function () { console.log('anonymous function expression'); }; } في الدوال العبارات المُسماة يُرفع اسم المتغير، بخلاف اسم الدالة أو متنها. function example() { console.log(named); // => undefined named(); // => TypeError named is not a function superPower(); // => ReferenceError superPower is not defined var named = function superPower() { console.log('Flying'); }; } // الأمر نفسه يحدث عندما يكون اسم الدالة // هو نفسه اسم المتغيّر. function example() { console.log(named); // => undefined named(); // => TypeError named is not a function var named = function named() { console.log('named'); }; } يرفع التصريح بالدوال اسم الدالة ومتنها إلى أعلى النطاق. function example() { superPower(); // => Flying function superPower() { console.log('Flying'); } } عوامل المقارنة والمساواة استخدم === و !== بدلًا من == و !=. تقيّم التعليمات الشرطية مثل العبارة if العبارات عبر آلية الإجبار (Coercion) عن طريق الدالة المجرَّدة ToBoolean والتي تتبع دائمًا القواعد البسيطة التالية: تُمنَح للكائنات القيمة true تُعيَّن للنوع Undefined القيمة false تُحدَّد للعبارة Null القيمة false البيانات من النوع المنطقي (Booleans) تُعطى لها القيمة المنطقية الموافقة إذا كانت قيمة العدد تساوي 0+ أو 0- أو NaN تحدّد قيمته المنطقية بـ false، وإلّا يأخذ القيمة true إذا كانت سلسلة المحارف (String) خاوية ("") تُعيَّن قيمتها إلى false، وإلاّ تأخذ القيمة true. if ([0] && []) { // true // المصفوفة (حتى وإنْ كانت فارغة) هي كائن، والكائنات تُقيّم بالقيمة true } استخدم الاختصارات عند التعامل مع قيم منطقية، بالمقابل استخدم المقارنات الصريحة للنصوص والأرقام. // سيئ if (isValid === true) { // ... } // جيّد if (isValid) { // ... } // سيئ if (name) { // ... } // جيّد if (name !== '') { // ... } // سيئ if (collection.length) { // ... } // جيّد if (collection.length > 0) { // ... } استخدم الأقواس المعقوصة ({}) لإنشاء الكتل في البنود case و default التي تحتوي التصريح بمتغيّرات (على سبيل المثال let، const ، function ، وclass). استعن بالقاعدة no-case-declarations. لماذا؟ التصريحات مرئية في كامل كتلة switch، ولكن لا يُعاد تعيينها إلّا عندما تُسنَد لها قيمة، وهو ما لا يحدث إلا عند بلوغ بند case، وهو ما يسبب مشاكل عندما يحاول أكثر من بند case تعريف الشيء نفسه. // سيئ switch (foo) { case 1: let x = 1; break; case 2: const y = 2; break; case 3: function f() { // ... } break; default: class C {} } // جيّد switch (foo) { case 1: { let x = 1; break; } case 2: { const y = 2; break; } case 3: { function f() { // ... } break; } case 4: bar(); break; default: { class C {} } } لا ينبغي أن تتداخل عوامل المقارنة الثلاثية Ternaries، وفي الغالب ينبغي أن تكون على سطر واحد. استعن بقاعدة no-nested-ternary. // سيئ const foo = maybe1 > maybe2 ? "bar" : value1 > value2 ? "baz" : null; // التقسيم إلى عبارتين تستخدمان مقارنات ثلاثية const maybeNull = value1 > value2 ? 'baz' : null; // أفضل const foo = maybe1 > maybe2 ? 'bar' : maybeNull; // الأفضل const foo = maybe1 > maybe2 ? 'bar' : maybeNull; تجنب استخدام المقارنات الثلاثية التي لا لزوم لها. استعن بقاعدة no-unneeded-ternary. // سيئ const foo = a ? a : b; const bar = c ? true : false; const baz = c ? false : true; // جيّد const foo = a || b; const bar = !!c; const baz = !c; استخدم الأقواس عند مزج العمليات، والاستثناء الوحيد هي العمليات الحسابية القياسية (+، -، *، /) بما أنّ الأسبقية فيها مفهومة جيّدا. ننصح بوضع / و* بين قوسين، لأنّ الأسبقية بينهما قد تكون غير واضحة عند المزج بينهما. استعن بقاعدة no-mixed-operators. لماذا؟ هذا يحسّن المقروئية ويوضّح قصد المطوّر. // سيئ const foo = a && b < 0 || c > 0 || d + 1 === 0; // سيئ const bar = a ** b - 5 % d; // سيئ // one may be confused into thinking (a || b) && c if (a || b && c) { return d; } // جيّد const foo = (a && b < 0) || c > 0 || (d + 1 === 0); // جيّد const bar = (a ** b) - (5 % d); // جيّد if (a || (b && c)) { return d; } // جيّد const bar = a + b / c * d; الكتل Blocks استخدم الأقواس المعقوصة للكتل متعددة الأسطر. // سيئ if (test) return false; // جيّد if (test) return false; // جيّد if (test) { return false; } // سيئ function foo() { return false; } // جيّد function bar() { return false; } إذا كنت تستخدم الكتل متعددة الأسطر مع if و else ، ضع else على السطر نفسه الذي يوجد عليه القوس المعقوص الذي يغلق كتلة if. // سيئ if (test) { thing1(); thing2(); } else { thing3(); } // جيّد if (test) { thing1(); thing2(); } else { thing3(); } إذا كانت كتلة if تُرجع قيمة دائمًا (أي تنتهي بالتعليمة return)، فكتلة else المتعلّقة بها غير ضرورية. تعليمة return في كتلة else if موالية لكتلة if تحتوي على return يمكن تقسيمها إلى عدة كتل if. استعن بقاعدة no-else-return. // سيئ function foo() { if (x) { return x; } else { return y; } } // سيئ function cats() { if (x) { return x; } else if (y) { return y; } } // سيئ function dogs() { if (x) { return x; } else { if (y) { return y; } } } // جيّد function foo() { if (x) { return x; } return y; } // جيّد function cats() { if (x) { return x; } if (y) { return y; } } //جيّد function dogs(x) { if (x) { if (z) { return y; } } else { return z; } } تعليمات التحكم Control Statements إذا تجاوزت عبارات التحكم (if, while …) الحد الأقصى لطول السطر، فيمكن وضع كل شرط (أو مجموعة من الشروط) في سطر جديد. كما يجب أن يكون العامل المنطقي في بداية السطر. لماذا؟ وضع العوامل في بداية السطر يحافظ على محاذاة العوامل ويتبع نمطًا مماثلا لتسلسل التوابع. كما يحسّن القراءة من خلال توضيح العمليات المنطقية المعقدة. // سيئ if ((foo === 123 || bar === 'abc') && doesItLookGoodWhenItBecomesThatLong() && isThisReallyHappening()) { thing1(); } // سيئ if (foo === 123 && bar === 'abc') { thing1(); } // سيئ if (foo === 123 && bar === 'abc') { thing1(); } // سيئ if ( foo === 123 && bar === 'abc' ) { thing1(); } // جيّد if ( foo === 123 && bar === 'abc' ) { thing1(); } // جيّد if ( (foo === 123 || bar === "abc") && doesItLookجيّدWhenItBecomesThatLong() && isThisReallyHappening() ) { thing1(); } // جيّد if (foo === 123 && bar === 'abc') { thing1(); } لا تستخدم عوامل الاختيار بدلًا من تعليمات التحكم // سيّئ !isRunning && startRunning(); // جيّد if (!isRunning) { startRunning(); } التعليقات استخدم /** ... */ للتعليقات متعددة الأسطر. // سيئ // make() returns a new element // based on the passed in tag name // // @param {String} tag // @return {Element} element function make(tag) { // ... return element; } // جيّد /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; } استخدم // لأجل التعليقات ذات السطر الواحد. ضع التعليقات ذات السطر الواحد على سطر جديد فوق التعليمة البرمجية موضوع التعليق. وضع سطرًا فارغًا قبل التعليق إلا إذا كان على السطر الأول من كتلة. // سيئ const active = true; // is current tab // جيّد // is current tab const active = true; // سيئ function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // جيّد function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type; } // جيّد أيضا function getType() { // set the default type to 'no type' const type = this.type || 'no type'; return type; } ابدأ كل التعليقات بمسافة لجعلها أسهل للقراءة. استعت بقاعدة spaced-comment. // سيئ //is current tab const active = true; // جيّد // is current tab const active = true; // سيئ /** *make() returns a new element *based on the passed-in tag name */ function make(tag) { // ... return element; } // جيّد /** * make() returns a new element * based on the passed-in tag name */ function make(tag) { // ... return element; } ابدأ التعليقات التي تهدف إلى لفت انتباه المطورين الآخرين إلى أنّ هناك مشكلةً تحتاج إلى المراجعة بالكلمات FIXME أو TODO، أو إذا كنت تقترح حلّا لتنفيذه. تختلف هذه عن التعليقات العادية لأنها قابلة للتنفيذ. الإجراءات قد تكون: FIXME: -- need to figure this out أو TODO: -- need to implement مثلا. استخدم // FIXME: للإشارة إلى مشكلة. class Calculator extends Abacus { constructor() { super(); // FIXME: shouldn’t use a global here total = 0; } } استخدم // TODO: لاقتراح حل لمشكلة. class Calculator extends Abacus { constructor() { super(); // TODO: total should be configurable by an options param this.total = 0; } } المسافات Whitespace أضف فراغيْن بسيطيْن (زر المسافة مرتيْن) للمسافات البادئة. استعن بقاعدة indent. // سيئ function foo() { ∙∙∙∙let name; } // سيئ function bar() { ∙let name; } // جيّد function baz() { ∙∙let name; } ضع مسافة واحدة قبل القوس المعقوص الأول. استعن بالقاعدة space-before-blocks. // سيئ function test(){ console.log('test'); } // جيّد function test() { console.log('test'); } // سيئ dog.set('attr',{ age: '1 year', breed: 'Bernese Mountain Dog', }); // جيّد dog.set('attr', { age: '1 year', breed: 'Bernese Mountain Dog', }); ضع مسافةً قبل القوس الفاتح في تعليمات التحكم (if, while …). لا تضع أي مسافات بين لائحة الوسائط Arguments واسم الدالة عند استدعاء دالة أو التصريح بها. استعن بقاعدة keyword-spacing. // سيئ if(isJedi) { fight (); } // جيّد if (isJedi) { fight(); } // سيئ function fight () { console.log ('Swooosh!'); } // جيّد function fight() { console.log('Swooosh!'); } ضع مسافات بين العوامل. استعن بقاعدة space-infix-ops. // سيئ const x=y+5; // جيّد const x = y + 5; أنهِ الملفات بمحرف الرجوع إلى السطر. استعن بقاعدة eol-last // سيئ import { es6 } from './AirbnbStyleGuide'; // ... export default es6; // سيئ import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ ↵ // جيّد import { es6 } from './AirbnbStyleGuide'; // ... export default es6;↵ استخدم المسافة البادئة عند استخدام سلسة طويلة من التوابع (أكثر من اثنتين). استخدم نقطة في البداية، لكي تبين أنّ السطر هو استدعاء لتابع، وليس تعليمةً جديدة. استعن بالقاعدتيْن newline-per-chained-call وno-whitespace-before-property. // سيئ $('#items').find('.selected').highlight().end().find('.open').updateCount(); // سيئ $('#items'). find('.selected'). highlight(). end(). find('.open'). updateCount(); // جيّد $('#items') .find('.selected') .highlight() .end() .find('.open') .updateCount(); // سيئ const leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true) .attr('width', (radius + margin) * 2).append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // جيّد const leds = stage.selectAll('.led') .data(data) .enter().append('svg:svg') .classed('led', true) .attr('width', (radius + margin) * 2) .append('svg:g') .attr('transform', `translate(${radius + margin},${radius + margin})`) .call(tron.led); // جيّد const leds = stage.selectAll('.led').data(data); ضع سطرًا فارغًا بعد الكتل وقبل التعليمة الموالية. // سيئ if (foo) { return bar; } return baz; // جيّد if (foo) { return bar; } return baz; // سيئ const obj = { foo() { }, bar() { }, }; return obj; // جيّد const obj = { foo() { }, bar() { }, }; return obj; // سيئ const arr = [ function foo() { }, function bar() { }, ]; return arr; // جيّد const arr = [ function foo() { }, function bar() { }, ]; return arr; لا تحش الكتل بأسطر فارغة. استعن بالقاعدة padded-blocks. // سيئ function bar() { console.log(foo); } // سيئ if (baz) { console.log(qux); } else { console.log(foo); } // سيئ class Foo { constructor(bar) { this.bar = bar; } } // جيّد function bar() { console.log(foo); } // جيّد if (baz) { console.log(qux); } else { console.log(foo); } لا تستخدم عدة أسطر فارغة لحشو شفرتك البرمجية. استعن بالقاعدة no-multiple-empty-lines. // سيّئ class Person { constructor(fullName, email, birthday) { this.fullName = fullName; this.email = email; this.setAge(birthday); } setAge(birthday) { const today = new Date(); const age = this.getAge(today, birthday); this.age = age; } getAge(today, birthday) { // .. } } // جيّد class Person { constructor(fullName, email, birthday) { this.fullName = fullName; this.email = email; this.setAge(birthday); } setAge(birthday) { const today = new Date(); const age = getAge(today, birthday); this.age = age; } getAge(today, birthday) { // .. } } لا تضف مسافات داخل الأقواس. استعن بالقاعدة space-in-parens. // سيئ function bar( foo ) { return foo; } // جيّد function bar(foo) { return foo; } // سيئ if ( foo ) { console.log(foo); } // جيّد if (foo) { console.log(foo); } لا تضف مسافات داخل الأقواس المعكوفة ([]). استعن بالقاعدة array-bracket-spacing. // سيئ const foo = [ 1, 2, 3 ]; console.log(foo[ 0 ]); // جيّد const foo = [1, 2, 3]; console.log(foo[0]); أضف مساحات داخل الأقواس المعقوصة. استعن بالقاعدة object-curly-spacing. // سيئ const foo = {clark: 'kent'}; // جيّد const foo = { clark: 'kent' }; تجنب التعليمات البرمجية التي يتجاوز طولها 100 محرف (باحتساب المسافات). النصوص الطويلة - حسب قاعدة مذكورة أعلاه - مستثناة من هذه القاعدة، وينبغي ألّا تُفكك. استعن بالقاعدة max-len. لماذا؟ لضمان سهولة القراءة والصيانة. // سيئ const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // سيئ $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' } }).done(() => console.log('Congratulations!')).fail(() => console.log('You have failed this city.')); // جيّد const foo = jsonData && jsonData.foo && jsonData.foo.bar && jsonData.foo.bar.baz && jsonData.foo.bar.baz.quux && jsonData.foo.bar.baz.quux.xyzzy; // جيّد $.ajax({ method: 'POST', url: 'https://airbnb.com/', data: { name: 'John' }, }) .done(() => console.log('Congratulations!')) .fail(() => console.log('You have failed this city.')); افرض مسافات متناسقة بعد القوس المعقوص البادئ لكتلة وبعد القوس المعقوص البادئ لكتلة موالية لها على السطر نفسه. الأمر نفسه ينطبق على الأقواس المعقوصة المكمّلة للكتلتيْن. استعن بقاعدة block-spacing. // سيّئ function foo() {return true;} if (foo) { bar = 0;} // جيّد function foo() { return true; } if (foo) { bar = 0; } تجنّب المسافات قبل الفواصل وافرض مسافة بعد الفاصلة. استعن بالقاعدة comma-spacing. // جيّد var foo = 1,bar = 2; var arr = [1 , 2]; // سيّئ var foo = 1, bar = 2; var arr = [1, 2]; افرض عدم استخدام المسافات داخل الأقواس المعكوفة لخاصيّة محسوبة. استعن بالقاعدة computed-property-spacing. // سيّئ obj[foo ] obj[ 'foo'] var x = {[ b ]: a} obj[foo[ bar ]] // جيّد obj[foo] obj['foo'] var x = { [b]: a } obj[foo[bar]] تجنّب المسافات بين دالة واستدعائها. استعن بالقاعدة func-call-spacing. // سيئ func (); func (); // جيّد func(); افرض المسافات بين المفاتيح والقيم في الخاصيّات المصنّفة النوع في الكائنات. استعن بالقاعدة key-spacing. // سيّئ var obj = { foo : 42 }; var obj2 = { foo:42 }; // جيّد var obj = { foo: 42 }; تجنّب المسافات الباقية بعد نهاية الأسطر. استعن بالقاعدة no-trailing-spaces. تجنّب عدة أسطر فارغة، ولاتسمح إلا بسطر واحد جديد في نهاية الملفات. تجنّب كذلك وجود سطر جديد في بداية الملفات. استعن بالقاعدة no-multiple-empty-lines. // سيّئ - عدة أسطر فارغة. var x = 1; var y = 2; // سيّئ - سطران جديدان بعد نهاية الملف var x = 1; var y = 2; // سيّئ - سطر جديد في بداية الملف var x = 1; var y = 2; // جيّد var x = 1; var y = 2; الفواصل تجنب الفواصل في البداية. استعن بالقاعدة comma-style. // سيئ const story = [ once , upon , aTime ]; // جيّد const story = [ once, upon, aTime, ]; // سيئ const hero = { firstName: 'Ada' , lastName: 'Lovelace' , birthYear: 1815 , superPower: 'computers' }; // جيّد const hero = { firstName: 'Ada', lastName: 'Lovelace', birthYear: 1815, superPower: 'computers', }; استخدم فاصلة إضافية. استعن بالقاعدة comma-dangle. لماذا؟ هذا يؤدي إلى توضيح الاختلافات بين الشفرات البرمجية في إيداعات Git . علاوة على ذلك، تحذف مصرّفات مثل Babel الفواصل الزائدة في الشفرة الناتجة عن التصريف، ممّا يعني أنه لا داعي للقلق من مشكلة الفاصلة المُجرجَرة (Trailing comma) في المتصفحات القديمة. // سيئ، فرق بين إيداعين في Git بدون فاصلة إضافية const hero = { firstName: 'Florence', - lastName: 'Nightingale' + lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'] }; // جيّد، فرق بين إيداعين في Git بوجود فاصلة إضافية const hero = { firstName: 'Florence', lastName: 'Nightingale', + inventorOf: ['coxcomb chart', 'modern nursing'], }; // سيئ const hero = { firstName: 'Dana', lastName: 'Scully' }; const heroes = [ 'Batman', 'Superman' ]; // جيّد const hero = { firstName: 'Dana', lastName: 'Scully', }; const heroes = [ 'Batman', 'Superman', ]; // سيئ function createHero( firstName, lastName, inventorOf ) { // does nothing } // جيّد function createHero( firstName, lastName, inventorOf, ) { // does nothing } // جيّد (انتبه إلى أنه يجب ألا تظهر فاصلة بعد عامل الاستناد Rest element) function createHero( firstName, lastName, inventorOf, ...heroArgs ) { // does nothing } // سيئ createHero( firstName, lastName, inventorOf ); // جيّد createHero( firstName, lastName, inventorOf, ); // جيّد (انتبه إلى أنه يجب ألا تظهر فاصلة بعد عامل الاستناد Rest element) createHero( firstName, lastName, inventorOf, ...heroArgs ); الفاصلة المنقوطة Semicolons استخدم الفاصلة المنقوطة. استعن بالقاعدة semi. لماذا؟ عندما يصادف مفسّر جافاسكريبت عودة إلى السطر بدون فاصلة منقوطة، فإنه يستخدم مجموعة من القواعد تسمى الإدراج التلقائي للفاصلة المنقوطة (Automatic Semicolon Insertion) لتحديد ما إذا كان يجب احتساب نهاية السطر على أنها نهاية للتعليمة البرمجية، ويُدرِج، كما يوحي الاسم، فاصلة منقوطة في الشفرة البرمجية قبل نهاية السطر إذا رأى أن التعليمة قد انتهت. الإدراج التلقائي للفاصلة المنقوطة ترافقه سلوكيات شاذة، قد تتسبّب في إساءة فهم الشفرة البرمجية. تصبح هذه القواعد أكثر تعقيدًا مع إضافة ميزات جديدة إلى جافاسكريبت. سيساعد الوضوح في إنهاء التعليمات البرمجية وإعداد أداة جودة الشفرة (مثل ESLint) لتحديد الفواصل المنقوطة المفقودة في تجنب تلك المشاكل. // سيئ، تنتج عنه استثناءات (Exceptions) const luke = {} const leia = {} [luke, leia].forEach(jedi => jedi.father = 'vader') // سيئ، تنتج عنه استثناءات (Exceptions) const reaction = "No! That's impossible!" (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()) // سيّئ، يُرجع `undefined` بدلًا من القيمة الموجودة في السطر الموالي // يحدث هذا دائمًا عندما تكون التعليمة return مفصولة بسطر عن القمية المرجَعة // وذلك بسبب حدوث الإدراج التلقائي للفاصلة المنقوطة function foo() { return 'search your feelings, you know it to be foo' } // جيّد const luke = {}; const leia = {}; [luke, leia].forEach((jedi) => { jedi.father = 'vader'; }); // جيّد const reaction = "No! That's impossible!"; (async function meanwhileOnTheFalcon(){ // handle `leia`, `lando`, `chewie`, `r2`, `c3p0` // ... }()); // جيّد function foo() { return 'search your feelings, you know it to be foo'; } التحويل بين أنواع البيانات وفرض نوع معيّن Type Casting & Coercion حوّل نوع البيانات في بداية التعليمة. سلاسل المحارف: // => this.reviewScore = 9; // سيئ const totalScore = new String(this.reviewScore); // المتغيّر totalScore كائن "object" وليس سلسلة محارف "string" // سيئ const totalScore = this.reviewScore + ''; // يستدعي التابع this.reviewScore.valueOf() // سيئ const totalScore = this.reviewScore.toString(); // لا تضمن إرجاع سلسلة محارف // جيّد const totalScore = String(this.reviewScore); بالنسبة للأعداد: استخدم Number لأجل التحويل، أما لتحليل النصوص فاستخدم دائمًا التابع parseInt مع أساس radix . استعن بالقاعدتيْن radix وno-new-wrappers. const inputValue = '4'; // سيئ const val = new Number(inputValue); // سيئ const val = +inputValue; // سيئ const val = inputValue >> 0; // سيئ const val = parseInt(inputValue); // جيّد const val = Number(inputValue); // جيّد const val = parseInt(inputValue, 10); ضع تعليقًا يشرح ما الذي تفعله ولماذا إذا كنت مضطرّا لاستعمال parseInt واحتجت إلى استخدام Bitshift لأسباب تتعلق بالأداء. // جيّد /** * parseInt was the reason my code was slow. * Bitshifting the String to coerce it to a * Number made it a lot faster. */ const val = inputValue >> 0; احذر عند استخدام عمليات الإزاحة bitshift ، فالأعداد مُمثلة بقيم من 64 بتا. ولكنّ عمليات الإزاحة دائما تُرجع أعدادًا ممثلة بقيم من 32 بت (المرجع). يمكن أن تؤدي الإزاحة إلى نتائج غير متوقعة عند استخدام قيم عددية أكبر من 32 بتا. أكبر قيمة للأعداد ذات الإشارة الممثلة على 32 بتا هي 2,147,483,647. (نقاش). 2147483647 >> 0; // => 2147483647 2147483648 >> 0; // => -2147483648 2147483649 >> 0; // => -2147483647 القيم المنطقية: const age = 0; // سيئ const hasAge = new Boolean(age); // جيّد const hasAge = Boolean(age); // best const hasAge = !!age; اصطلاحات التسمية تجنب الأسماء المكونة من حرف واحد. استخدم أسماء معبّرة. استعن بالقاعدة id-length. // سيئ function q() { // ... } // جيّد function query() { // ... } استخدم أسلوب camelCase لتسمية الكائنات، والدوال، والنظائر (Instances). استعن بالقاعدة camelCase. // سيئ const OBJEcttsssss = {}; const this_is_my_object = {}; function c() {} // جيّد const thisIsMyObject = {}; function thisIsMyFunction() {} لا تستخدم أسلوب التسمية PascalCase إلا عند تسمية المُنشئات أو الأصناف. استعن بالدالة new-cap. // سيئ function user(options) { this.name = options.name; } const سيئ = new user({ name: 'nope', }); // جيّد class User { constructor(options) { this.name = options.name; } } const جيّد = new User({ name: 'yup', }); لا تستخدم العارضة السفلية في البداية أو النهاية. استعن بالقاعدة camelcno-underscore-danglease. لماذا؟ لا يوجد في جافاسكريبت مفهوم الخصوصية عندما يتعلّق الأمر بالخاصيّات أو التوابع. على الرغم من أن الكثيرين ينظرون إلى وضع العارضة السفلية في بداية الاسم على أنه اصطلاح يعني “خاص”، إلّا أن هذه الخاصيّات عامة كلها، وعلى هذا النحو، فهي جزء من الواجهة البرمجية API العمومية. هذا الاصطلاح قد يؤدي بالمطورين للاعتقاد خطأً بأن التغيير لن يؤثّر سلبًا على الشفرة البرمجية، أو أنه ليست هناك حاجة للاختبار. إن بدا لك هذا الشرح طويلًا، فتذكر هذه الجملة: إذا كنت تريد لشيء أن يكون “خاصّا”، فيجب ألا تضعه في مكان ملحوظ. // سيئ this.__firstName__ = 'Panda'; this.firstName_ = 'Panda'; this._firstName = 'Panda'; // جيّد this.firstName = 'Panda'; // جيّد، في البيئات التي يكون WeakMaps متوفّرًا فيها، راجع الرابط التالي // see https://kangax.github.io/compat-table/es6/#test-WeakMap const firstNames = new WeakMap(); firstNames.set(this, 'Panda'); لا تحفظ مرجعًا إلى this. واستخدم الدوال السهمية أو التابع Function#bind . // سيئ function foo() { const self = this; return function () { console.log(self); }; } // سيئ function foo() { const that = this; return function () { console.log(that); }; } // جيّد function foo() { return () => { console.log(this); }; } يجب أن يتطابق اسم الملف القاعدي (Base filename) تمامًا مع اسم التصدير الافتراضي. // file 1 contents class CheckBox { // ... } export default CheckBox; // file 2 contents export default function fortyTwo() { return 42; } // file 3 contents export default function insideDirectory() {} // in some other file // سيئ import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export // سيئ import CheckBox from './check_box'; // PascalCase import/export, snake_case filename import forty_two from './forty_two'; // snake_case import/filename, camelCase export import inside_directory from './inside_directory'; // snake_case import, camelCase export import index from './inside_directory/index'; // requiring the index file explicitly import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly // جيّد import CheckBox from './CheckBox'; // PascalCase export/import/filename import fortyTwo from './fortyTwo'; // camelCase export/import/filename import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index" // ^ supports both insideDirectory.js and insideDirectory/index.js استخدم أسلوب التسمية camelCase عندما التصدير الافتراضي لدالة. يجب أن يكون اسم الملف الخاص بك مطابقًا لاسم دالتك. function makeStyleGuide() { // ... } export default makeStyleGuide; استخدم أسلوب التسمية PascalCase عندما تصدّر منشئًا أو صنفًا أو صنفًا أو مكتبة دوال أو كائن مجرّدا. const AirbnbStyleGuide = { es6: { }, }; export default AirbnbStyleGuide; يجب أن تكون كل حروف المختصرات والكلمات المنحوتة إما مكتوبة بأحرف كبيرة وإما بأحرف صغيرة. لماذا؟ تهدف التسميات لتسهيل قراءة الشفرة على الإنسان، وليس لاسترضاء خوارزميات الكمبيوتر. // سيئ import SmsContainer from './containers/SmsContainer'; // سيئ const HttpRequests = [ // ... ]; // جيّد import SMSContainer from './containers/SMSContainer'; // جيّد const HTTPRequests = [ // ... ]; // جيّد أيضا const httpRequests = [ // ... ]; // أفضل import TextMessageContainer from './containers/TextMessageContainer'; // أفضل const requests = [ // ... ]; اختياريًّا، يمكنك كتابة ثابت بحروف كبيرة إذا تحقّقت الشروط التالية: 1) التصدير، 2) التصريح بالكلمة const ، أي أنه لا يمكن إعادة إسناده، 3) يمكن للمبرمج أن يثق أنه لم يتغيّر لا هو ولا الخاصيّات المتفرّعة عنه. لماذا؟ هذه أداة إضافية للحالات التي يكون المبرمج فيها غير متأكد من أن المتغيّر ستتغيّر قيمته. تخبر المتغيّرات المكتوبة بحروف كبيرة (مثل UPPERCASE_VARIABLES) المبرمج أن بإمكانه الوثوق من أن تلك الثوابت (وخاصيّاتها) لن تتغيّر قيمتها. هل ينطبق الأمر على كل المتغيّرات المعرّفة بالكلمة const؟ لا حاجة لذلك، وبالتالي يجب ألا تُستخدَم الحروف الكبيرة في تسمية الثوابت داخل ملف، ولكنها يجب أن تُستخدَم للثوابت المُصدَّرة. ماذا عن الكائنات المُصدَّرة؟ استخدم الحروف الكبيرة في المستوى الأعلى من التصدير (مثلًا EXPORTED_OBJECT.key) وتأكّد من أن الخاصيّات المتفرّعة كلها لا تتغيّر. // سيّئ const PRIVATE_VARIABLE = 'should not be unnecessarily uppercased within a file'; // سيّئ export const THING_TO_BE_CHANGED = 'should obviously not be uppercased'; // سيّئ export let REASSIGNABLE_VARIABLE = 'do not use let with uppercase variables'; // --- // مرخَّص به، لكنه لا يضيف قيمة دلالية export const apiKey = 'SOMEKEY'; // أفضل في أغلب الحالات export const API_KEY = 'SOMEKEY'; // --- // سيّء، حروف كبيرة غير ضرورية في الاسم مع انعدام القيمة الدلالية export const MAPPING = { KEY: 'value' }; // جيّد export const MAPPING = { key: 'value' }; المسترجعات (Accessors) ليس مفروضًا وجود توابع الاسترجاع للوصول إلى الخاصيّات. لا تستخدم توابع الاسترجاع أو التعديل التي توفّرها جافاسكريبت لأنّ لها آثارًا جانبيةً غير متوقعة، ويصعب اختبارها وصيانتها والتعامل معها. إنْ أردت استخدام المسترجعات (أو المعدّلات) فمن الجيّد استخدام التوابع ()getVal و('setVal('hello لهذا الغرض. // سيئ class Dragon { get age() { // ... } set age(value) { // ... } } // جيّد class Dragon { getAge() { // ... } setAge(value) { // ... } } استخدم()isVal أو ()hasVal للخاصيّات والتوابع المنطقية. // سيئ if (!dragon.age()) { return false; } // جيّد if (!dragon.hasAge()) { return false; } لا ضير في إنشاء دوال ()get و ()set، ولكن يجب أن تكون متسقة. class Jedi { constructor(options = {}) { const lightsaber = options.lightsaber || 'blue'; this.set('lightsaber', lightsaber); } set(key, val) { this[key] = val; } get(key) { return this[key]; } } الأحداث عند ربط حمولات البيانات بالأحداث (سواء كانت أحداث DOM أوأحداث مكتبات خاصّة مثل Backbone)، مرّر كائنًا مصنّف النوع (معروف أيضًا باسم “hash”) بدلًا من قيمة خام. سيسمح ذلك لاحقًا بإضافة المزيد من البيانات إلى حمولة الحدث دون الحاجة إلى إيجاد وتحديث كل معالجات الحدث. على سبيل المثال، بدلًا من: // سيئ $(this).trigger('listingUpdated', listing.id); // ... $(this).on('listingUpdated', (e, listingID) => { // do something with listingID }); من الأفضل استخدام: // جيّد $(this).trigger('listingUpdated', { listingID: listing.id }); // ... $(this).on('listingUpdated', (e, data) => { // do something with data.listingID }); jQuery ضع السابقة $ قبل متغيرات jQuery . // سيئ const sidebar = $('.sidebar'); // جيّد const $sidebar = $('.sidebar'); // جيّد const $sidebarBtn = $('.sidebar-btn'); أضف عمليات البحث المؤقت في jQuery إلى التخبئة (Cache). // سيئ function setSidebar() { $('.sidebar').hide(); // ... $('.sidebar').css({ 'background-color': 'pink', }); } // جيّد function setSidebar() { const $sidebar = $('.sidebar'); $sidebar.hide(); // ... $sidebar.css({ 'background-color': 'pink', }); } بالنسبة لاستعلامات DOM استخدم $('.sidebar ul') أو parent > child $('.sidebar > ul'). راجع jsperf. استخدم التابع find في الاستعلام عن الكائنات في نطاق jQuery. // سيئ $('ul', '.sidebar').hide(); // سيئ $('.sidebar').find('ul').hide(); // جيّد $('.sidebar ul').hide(); // جيّد $('.sidebar > ul').hide(); // جيّد $sidebar.find('ul').hide(); المكتبة القياسية تحوي المكتبة القياسية أدوات مساعدة لم تعد تُستخدم ولكن يُيقى عليها للتوافق مع المتصفحات القديمة. استخدم التابع Number.isNaN بدلًا من التابع العام isNaN. استعن بالقاعدة no-restricted-globals. لماذا؟ يحوّل التابع isNaN القيم غير العددية إلى أعداد، ويُرجع القيمة true لأي شيء يتحوّل إلى NaN. إذا كان السلوك هو ما ترغب فيه، فكن صريحًا في ذلك. // سيئ isNaN('1.2'); // false isNaN('1.2.3'); // true // جيّد Number.isNaN('1.2.3'); // false Number.isNaN(Number('1.2.3')); // true استخدم التابع Number.isFinite بدلًا من التابع العام isFinite. استعن بالقاعدة no-restricted-globals. لماذا؟ isFinite تحوّل القيم غير العددية إلى أعداد، وتُرجع القيمة true لأي شيء يتحوّل إلى عدد منته. إذا كان السلوك هو ما ترغب فيه، فكن صريحًا في ذلك. // سيئ isFinite('2e3'); // true // جيّد Number.isFinite('2e3'); // false Number.isFinite(parseInt('2e3', 10)); // true الاختبار يجب أن تكتب اختبارات، وليس أساسيًّا الإطار الذي تستخدمه لذلك. المهم أن تكتبها. احرص على كتابة العديد من الدوال البسيطة والصغيرة، وقلّل من استخدام البيانات المتحوّلة . كن حذرًا عند استخدام أصناف stubs و mocks لأنها يمكن أن تجعل اختباراتك أكثر هشاشة. نستخدم mocha وjest في Airbnb. يُستخدَم tape كذلك من حين لآخر في وحدات صغيرة ومعزولة. محاولة اختبار 100٪ من الشفرة هو هدف جيد، حتى لو لم يكن دائمًا عمليّا. كلما أصلحت خللًا، قم بكتابة اختبار ارتداد (Regression test). فمن المؤكد أنّه بدونه ستعود الثغرات مجدّدا. ترجمة - وبتصرّف - للمقال Airbnb JavaScript Style Guide
-
المنهجية الأكثر منطقية لتنسيق شيفرة ملفات CSS وSASS المصطلحات التصريح عن قاعدة التصريح عن قاعدة (rule declaration): هو الاسم المختار لمحدّد معين (أو لمجموعة من المحددات) مع مجموعة من الخاصيات المصاحبة. إليك مثالًا لتوضيح الأمر: .listing { font-size: 18px; line-height: 1.2; } المحددات إن المحددات التي رأينها عند التصريح عن قاعدة، هي الجزء الّذي سيُحدد طريقة تنسيق العناصر في شجرة DOM، وذلك بحسب الخاصيات المعرّفة في المحدّد. يمكن أن تُطابقُ المحددات عناصر HTML، أو صنف العنصر (class)، أو معرّف العنصر (ID)، أو أي سِمة من سماته. وهذه بعض الأمثلة عن المحدّدات: .my-element-class { /* ... */ } [aria-hidden] { /* ... */ } الخاصيات أخيرًا، الخاصيات هي ما يمنح العناصر المحددة تنسيقها والتي صرحنا عنها في المحدد. والخاصيات هي أزواج مؤلفة من الخاصية وقيمتها، ويمكن للقاعدة المصرح عنها أن تحتوي على تعريف خاصية أو أكثر. وهكذا ستبدو طريقة التصريح عن الخاصيات: /* بعض المحددات */ { background: #f1f1f1; color: #333; } ملفات CSS التنسيق استخدم الزر (tabs) بمقدار مسافتين (وتسمى أيضًا Soft-Tab نظرًا لأن زر Tab الإفتراضي يكون ثمان مسافات). يفضل استخدام الشرطة العادية (وهي -) بدل تسمية الأصناف بأسلوب سنام الجمل (camelCasing). لا بأس باستخدام الشرطات السفلية والتسمية بأسلوب PascalCasing (وهي طريقة مشابهة لطريقة سنام الجمل إلا أنه يجب أن يكون أول حرف كبير) إن كنت تستخدم BEM ( والّتي سنتطرق إليها لاحقًا في هذا الدليل). لا تستخدم مُحدِّدات المُعرِّفات (ID). عند استخدام محددات متعددة في تصريح واحد لقاعدة ما، امنح كل مُحدّد سطر خاص به. ضع مسافة قبل قوس الافتتاح { عند التصريح عن قاعدة. ضع مسافة بعد : وليس قبله في الخاصيات. ضع أقواس الإغلاق } عند التصريح عن قاعدة في سطر جديد. ضع أسطر فارغة بين تصريحات القواعد. طريقة تنسيق سيئة .avatar{ border-radius:50%; border:2px solid white; } .no, .nope, .not_good { // ... } #lol-no { // ... } طريقة تنسيق جيدة .avatar { border-radius: 50%; border: 2px solid white; } .one, .selector, .per-line { // ... } التعليقات يفضلُ استخدام التعليقات السطرية (وهي // في Sass-land) للتعليقات الكتلية. يفضلُ أن تضع التعليق بسطر خاص به. تجنب التعليقات في نهاية السطر. اكتب تعليقات تفصيلية للشيفرة البرمجية التي لا تشرح نفسها بنفسها: استخدام الخاصية z-index. استخدام المميزات التوافقية أو بعض الخدع المخصصة للمتصفحات. OOCSS و BEM نشجع على استخدام مزيج من OOCSS و BEM وذلك للأسباب التالية: يساعدنا هذا المزيج في إنشاء علاقات واضحة وصارمة بين ملفات التنسيق CSS وملفات HTML. ينشئ مكونات قابلة لإعادة الاستخدام والتركيب. يسمح بتقليل التداخل والتخصيص. يساعد في زيادة قابلية تطوير ملفات التنسيق. OOCSS (اختصارًا "Object Oriented CSS") وهي التنسيق كائني التوجه لملفات التنسيق، هو نهج لكتابة تنسيق CSS يشجعك على التفكير بملفات التنسيق على أنها مجموعة من "الكائنات": أي أنها قابلة لإعادة استخدام أجزاءٍ منها، وقابلة للتكرار، ويمكن استخدامها بشكل مستقل عبر موقع الوِب. لمزيد من المعلومات يمكنك الاطلاع على: توثيق نيكول سوليفان (Nicole Sullivan's) OOCSS wiki. مقال مدخل إلى OOCSS (Introduction to OOCSS). BEM (اختصارًا Block-Element-Modifier) معدلّ عنصر الكتلة وهو عبارة عن اصطلاح تسمية للأصناف في HTML و CSS. طوّر في الأصل من خلال شركة Yandex ووضعوا في حسبانهم الشيفرات البرمجية الكبيرة وقابلية التطوّر والتوسع للشيفرة، ويمكن اعتبارها بمثابة مجموعة قوية من المبادئ التوجيهية لتطبيق OOCSS. لمزيد من المعلومات يمكنك الاطلاع على: خدع CSS دليلك إلى منهجية BEM - CSS مدخل إلى BEM (introduction to BEM). نوصي بالتنويع بأشكال BEM مع "كتل" مسماة بأسلوب PascalCased، والّتي تعمل بطريقة جيدة خصيصًا عند دمجها مع المكونات (مثلما يحدث في React). لا تزال تُستخدم الشُرط السفلية والشرطات للمعدلات وسلالتهم (من سيرثُ منهم). مثال // ListingCard.jsx function ListingCard() { return ( <article class="ListingCard ListingCard--featured"> <h1 class="ListingCard__title">Adorable 2BR in the sunny Mission</h1> <div class="ListingCard__content"> <p>Vestibulum id ligula porta felis euismod semper.</p> </div> </article> ); } /* ListingCard.css */ .ListingCard { } .ListingCard--featured { } .ListingCard__title { } .ListingCard__content { } .ListingCard: وهي عبارة عن "كتلة" وتمثل مكون عالي المستوى. .ListingCard__title: عبارة عن "عنصر" وتمثل سلالة .ListingCard التي تساعد في تكوين الكتلة ككلّ. .ListingCard--featured: وهو عبارة عن "معدلّ" ويمثل الحالات أو التشكيلات المتختلفة لكتلة .ListingCard. محددات المعرفات في حين أنه من الممكن تحديد العناصر حسب المعرّف في CSS، إلا أن هذه المحددات تعدّ أسلوبًا مضادًا للنمط (anti-pattern). تقدم محدّدات المعرّفات مستوى عاليًا من التخصيص غير ضروري للتصريح عن قاعدة، بل ولن يمكننا إعادة استخدام هذه المحدّدات. لمزيد من المعلومات حول هذا الموضوع، أحيلك لهذه المقالة CSS Wizardry's article والّتي تتحدث عن طريقة التعامل مع التخصيص. خطاف الجافاسكربت تجنب ربط نفس الصنف في كلّ من ملفات التنسيق CSS والجافاسكربت. لأنه غالبًا ما سيُؤدي لخلط الاثنين معًا، وبأحسن الأحوال سيؤدي ذلك لإضاعة الوقت أثناء إعادة البناء عندما يتعين على المطوّر العودة لكلّ صنف يغيره، وفي أسوأ الأحوال، سيخشى المطوّرون إجراء تعديلات عليها خوفًا من كسر الوظيفة ما المرتبطة بها. نوصي بإنشاء أصناف مخصصة للربط مع جافاسكربت مسبوقة بـ .js-: الحدود استخدم 0 بدلًا من none لتحديد أن النمط ليس له حدود. الطريقة السيئة .foo { border: none; } الطريقة الجيدة .foo { border: 0; } ملفات SASS الصياغة استخدم الصيغة .scss، ولا تستخدم أبدًا الصيغة .sass الأصلي رتب شيفرة ملفات CSS العادية وعمليات التضمين @include بطريقة منطقي (انظر أدناه) ترتيب طريقة التصريح عن الخاصيات التصريح عن خاصية أدرج جميع التصاريح القياسية للخاصيات، وأي شيء آخر ما عدا عمليات التضمين @include أو المحددات المتشعّية. .btn-green { background: green; font-weight: bold; // ... } تصريح عن عملية تضمين @include إن تجميع ووضع جميع عمليات التضمين @include في نهاية المحدّد سيسهل قراءة المحدد بالكامل. .btn-green { background: green; font-weight: bold; @include transition(background 0.5s ease); // ... } المحددات المتشعّبة يكون ترتيب المحددات المتشعّبة ( إن اضطررنا لاستخدامها) بعد عمليات التضمين ولا شيء بعدها. أضف مسافة بين عمليات التصريح عن القواعد والمحددات المتشعّبة، وكذلك بين المحددات المتشعّبة المتجاورة. طبّق نفس الإرشادات المذكورة سابقًا على المحددات المتشعّبة الخاصة بك. .btn { background: green; font-weight: bold; @include transition(background 0.5s ease); .icon { margin-right: 10px; } } المتغيرات يفضل استخدام أسماء المتغيرات التي تحتوي على شرطة عادية (مثل: $my-variable) بدلًا من أسماء متغيرات مثل أسلوب تسمية سنام الجمل (camelCased) -تكبير أول حرف من كلّ كلمة في اسم المتغير عدا أول كلمة- أو أسلوب snake_cased -جميع حروف اسم المتغير صغيرة-. لا مشكلة بإضافة شرطة سفلية لأسماء المتغيرات الّتي نود استخدامها فقط داخل نفس الملف(هكذا: $_my-variable). المخاليط يجب استخدام المخاليط (mixins) لتصغير شيفرتك البرمجية، أو لتوضيحها، أو لعزل تعقيدها - بطريقة مشابهة تمامًا للدوالّ المسماة جيدًا. يمكن للمخاليط الّتي لا تقبل أي وسطاء أن تكون مفيدة لذلك، ولكن انتبه فأذا لم تستخدم خوارزميات ضغط للملفات المشروع (مثل خوارزمية gzip)، فسيُساهم ذلك في تكرار الشيفرات البرمجية غير الضرورية في التنسيقات الناتجة. توسعة المحدّدات المُركّبة يجب تجنب التوسعة باستخدام التعليمة @extend لأنه يسلك سلوك غير معروف للبعض وغالبًا ما يكون خطير، خاصة عند استخدامه مع محددات متشعّبة. حتى إن توسيع محددات العناصر الأساسية عالية المستوى يمكن أن يسبب مشاكل إذا انتهى الأمر بتغيير ترتيب المحددات في وقت لاحق (فمثلًا إذا كانت هذه المحددات في ملفات أخرى، وتغير ترتيب تحميل الملفات فهذا سيُشكل مشكلة). يجب أن تتعامل خوارزمية الضغط Gzipping مع عمليات تصغير أحجام الملفات الّتي ستحصلُ عليها باستخدام @extend، وستمنحُك المخاليط تصغيرًا جيدًا لملفات التنسيق خاصتك. المحددات المتشعّبة ** لا تنشئ محددات متشعّبة بعمق يزيد عن ثلاثة مستويات!** .page-container { .content { .profile { // STOP! } } } عندما تصبح المحددات طويلة، من المحتمل أنك تضطر أن لتكتب تنسيق لديه المميزات التالية: مقترن بشدة بملفات HTML (وهذا يعدّ نقطة ضعف في التنسيق) -أو محدد للغاية (وهذا يعدّ نقطة قوة في التنسيق) -أو غير قابل لإعادة الاستخدام. مرة أخرى: لا تستخدم نهائيًا محددات المعرّفات المتشعبة! في حال وجب عليك استخدام محدّد المعرّف في البداية (ويجب أن تحاول بأقصى جهدك أن تتجنبه)، فلا يجب أن تكون متشعّبة أبدًا. وإن وقعت بهذه المشكلة، فعاود النظر في الشيفرة البرمجية أو أسأل نفسك لما كلّ هذا التخصيص الشديد؟ وإن أردت كتابة ملفات HTML وCSS ذات بنية جيدة، فيجب عليك ألا تستخدم محدّدات المعرّفات المتشعبة نهائيًا. ترجمة -وبتصرف- لدليل Airbnb لتنسيق ملفات CSS وSASS (Airbnb CSS / Sass Styleguide) الموجود على موقع GitHub
-
يكون التجديد في نموذج العمل Business model عادةً أكثر تأثيرًا من التجديد التقني، كما أنه أكثر إزعاجا منه وأصعب في الاستنساخ. رغم ذلك لا تزال الكثير من الشركات ترتكز على التجديد التقني للمنافسة. حالة Airbnb تأمّل Airbnb. ما يجعل هذه الشركة ناجحة جدًّا ليس أفضليّة تقنية، بل نموذج أعمالها الذي يوفر لهم تكلفة قريبة من الصفر؛ ما يجعلها تتفوّق على سلاسل الفنادق التقليدية التي تحتاج إلى بناء مساحة مادية أكبر بتكلفة كبيرة. وبدلاً من تكلفة الإعداد، يمكن لشركة Airbnb إضافة غرفة أخرى إلى مخزونها دون أي تكاليف تقريباً من خلال تمكين الناس من مشاركة منازلهم القائمة. هذا هو التجديد في نموذج الأعمال. وعلاوة على ذلك، فإنه من الصعب للغاية لسلسة الفنادق التقليدية تبديل نموذج أعمالها لتتناسب مع نموذج عمل Airbnb . البرمجيات مفتوحة المصدر ينطبق الشيء نفسه على البرمجيّات مفتوحة المصدر. صحيح أن المصادر المفتوحة تُنتِج في كثير من الأحيان برامج متفوقة تقنياً، إلا أنّ قوتها الحقيقية تكمُن من تجديد نموذج الأعمال: المشاركة في الإنشاء. البرمجيات مفتوحة المصدر مثل دروبال أو لينكس هي منتجات خُلقت نتيجة المشاركة، فقد ساهم الآلاف في بناء دروبال وتعزيز مكانته، والجميع يستفيد من ذلك. يُنتِج مجتمع كبير للبرمجيّات مفتوحة المصدر أكثر بكثير من منافس بنفس الحجم، كما يتشارك في تقاسم تكاليف الإنتاج وإستراتيجية الدخول في السوق. يشوّش نموذج عمل البرمجيات مفتوحة المصدر كثيرا على شركات البرمجيات الخاصة التي توجد بها أدوار منفصلة لكل من الإنتاج والاستهلاك، بالإضافة للتكاليف المرتفعة للإنتاج والدخول في السوق. في حين يمكن للشركات الراسخة نسخ الابتكارات التقنية الأساسية للمصدر المفتوح، إلا أنه يصعُب عليها التبديل من نموذج الأعمال الاحتكاري إلى نموذج الأعمال مفتوح المصدر، لأن ذلك يُؤثر على كيفية بنائها لبرامجها، وكيفية تحقيق الدخل من البرامج، بالإضافة لكيفية بيعها وتسويقها لبرامجها، وهيكل التكاليف، وأكثر من ذلك بكثير. ستخسر شركات البرمجة الاحتكاريّة لصالح مجتمعات المصادر المفتوحة المزدهرة. لا يمكنني تصور كيف لشركات مثل HP، Oracle، SAP أن تغيّر نموذج أعمالها بينما تعيش فصلا ماليّا بعد الآخر في الأسواق العامة. يمكن لتغيير نموذج أعمال هذه الشركات أن يعرقل عائداتها وبالتأكيد سيستغرق التغيير سنوات طويلة. حالة Amazon Web Services خذ خدمات أمازون على سبيل المثال، إنها واحدة من أكثر التطورات إزعاجا في عالم تكنولوجيا المعلومات في العقد الماضي. في حين أن عروض AWS غنية وغالباً ما تكون في صدارة المنافسة، إلا أن أكبر سبب لنجاح الشركة هو نموذج أعمالها. فخدمات أمازون لا تعتمد فقط على التسعير القائم على الاستهلاك (ادفع بقدر ما تستهلك)، ولكنها أيضاً مريحة في تشغيل الأعمال ذات هامش الربح المنخفض. بعد 10 سنوات على إطلاق AWS تقريباً، هناك كميات هائلة من البيانات تتحرك في سحابتها. في حين أن Oracle، SAP وHP لا تملك حتى الآن سحابة أعمال تنافسية. رغم أنه كان بإمكان كلّ واحدة من هذه الشركات أن تعوِّض تأخرها التقني أمام أمازون، إلا أنه لم يكن بإمكانها تعديل نموذج عملها القائم فعلا. خاتمة من السهل جدًّا أن تجدّد في نموذج العمل بالنسبة للشركات الناشئة مقارنةً بالشركات الراسخة. في الواقع فإن نموذج العمل المبتكر هو أفضل سلاح لديك ضد الشركات الكبيرة. قد يعطيك الابتكار التقني ميزة تنافسية لمدة تتراوح بين 6-18 شهراً لا غير، ولكن ميزة التجدي في نموذج العمل يمكن أن تمنحك ميزة تنافسية لسنوات قادمة. تركّز كثير من الشركات الناشئة على إنشاء تقنيات مبتكرة أو الحصول عليها من شركات احتكاريّة بهدف الفوز في السوق. تأتي الشركات الناشئة عادةً بابتكارات تقنية في نقاط عدّة، إلا أنّ ما يؤدّي إلى منظمة ناجحة تدوم طويلا هو الإبداع في نموذج العمل؛ إذ أن نماذج العمل المبتكرة تميل إلى أن تكون مستعصية على الاستنساخ مقارنة بالابتكارات التقنية. ترجمة - بتصرّف - للمقال Business model innovation beats technical innovation لصاحبه Dries Buytaert. حقوق الصورة البارزة محفوظة لـ Freepik