دليل أسلوب الترميز في OpenResty
API7.ai
December 15, 2022
تحتوي العديد من لغات التطوير على مواصفات ترميز خاصة بها لإخبار المطورين ببعض الاتفاقيات في المجال، للحفاظ على نمط كتابة الكود بشكل متسق، وتجنب بعض الأخطاء الشائعة. يُعد PEP 8 الخاص بـ Python مثالًا ممتازًا على ذلك، حيث قرأ جميع مطوري Python تقريبًا هذه المواصفات التي كتبها مؤلفو Python.
لا يمتلك OpenResty حتى الآن مواصفات ترميز خاصة به، ويتم مراجعة بعض المطورين بشكل متكرر وطلب تغيير نمط الكود الخاص بهم بعد تقديم طلبات السحب (PRs)، مما يستهلك الكثير من الوقت والجهد الذي يمكن تجنبه.
يوجد في OpenResty أداتان من أدوات Lint يمكن أن تساعدك في اكتشاف نمط الكود تلقائيًا: luacheck
و lj-releng
. الأولى هي أداة Lint شائعة في عالم Lua و OpenResty، بينما الثانية هي أداة Lint مكتوبة بـ Perl
بواسطة OpenResty نفسها.
بالنسبة لي، أقوم بتثبيت إضافة luacheck
في محرر VS Code حتى يكون لدي أداة تقدم اقتراحات تلقائية أثناء كتابة الكود؛ وفي CI للمشروع، أقوم بتشغيل الأداتين، على سبيل المثال:
luacheck -q lua
./utils/lj-releng lua/*.lua lua/apisix/*.lua
بعد كل شيء، وجود أداة إضافية للاختبار ليس أمرًا سيئًا أبدًا.
ومع ذلك، فإن هاتين الأداتين تهتمان أكثر باكتشاف المتغيرات العامة، طول السطر، وأنماط الكود الأساسية الأخرى، والتي لا تزال بعيدة عن التفاصيل الدقيقة لـ Python PEP 8، ولا يوجد وثائق يمكن الرجوع إليها.
لذلك، اليوم، بناءً على خبرتي في مشاريع OpenResty مفتوحة المصدر، قمت بتلخيص وثائق نمط الترميز لـ OpenResty. هذه المواصفات تتوافق أيضًا مع نمط الكود لبعض بوابات API مثل APISIX و Kong.
المسافة البادئة
في OpenResty، نستخدم 4 مسافات كعلامات للمسافة البادئة، على الرغم من أن Lua لا تتطلب مثل هذه القواعد. فيما يلي مثالان على الكود الخاطئ والصحيح.
--لا
if a then
ngx.say("hello")
end
--نعم
if a then
ngx.say("hello")
end
لتسهيل الأمر، يمكننا تبسيط العملية عن طريق تغيير Tab إلى 4 مسافات في المحرر الذي تستخدمه.
المسافة
على جانبي العامل، نحتاج إلى مسافة لفصلهما. فيما يلي مثالان على الكود الخاطئ والصحيح.
--لا
local i=1
local s = "apisix"
--نعم
local i = 1
local s = "apisix"
السطر الفارغ
يأتي العديد من المطورين باتفاقيات تطوير من لغات أخرى إلى OpenResty، مثل إضافة فاصلة منقوطة في نهاية السطر:
--لا
if a then
ngx.say("hello");
end;
ولكن في الواقع، إضافة الفواصل المنقوطة يجعل كود Lua يبدو قبيحًا جدًا، وهو أمر غير ضروري. أيضًا، يجب ألا تحول عدة أسطر من الكود إلى سطر واحد لتوفير الأسطر وجعل الكود مختصرًا. القيام بذلك سيتركك دون معرفة أي قسم من الكود به خطأ عند تحديد الخطأ.
--لا
if a then ngx.say("hello") end
--نعم
if a then
ngx.say("hello")
end
بالإضافة إلى ذلك، يجب فصل الدوال بأسطر فارغة.
--لا
local function foo()
end
local function bar()
end
--نعم
local function foo()
end
local function bar()
end
إذا كانت هناك فروع متعددة من if elseif
، فيجب أيضًا فصلها بأسطر فارغة.
--لا
if a == 1 then
foo()
elseif a== 2 then
bar()
elseif a == 3 then
run()
else
error()
end
--نعم
if a == 1 then
foo()
elseif a== 2 then
bar()
elseif a == 3 then
run()
else
error()
end
الحد الأقصى لطول السطر
يجب ألا يتجاوز كل سطر 80 حرفًا؛ إذا تجاوز ذلك، نحتاج إلى قطع السطر ومحاذاته. وعند محاذاة الأسطر المقطوعة، نحتاج إلى إظهار التطابق بين الأسطر العلوية والسفلية. بالنسبة للمثال أدناه، يجب أن تكون وسيطة الدالة في السطر الثاني على يمين القوس الأيسر في السطر الأول.
--لا
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst, conf.default_conn_delay)
--نعم
return limit_conn_new("plugin-limit-conn", conf.conn, conf.burst,
conf.default_conn_delay)
إذا كانت المحاذاة لسلسلة مقطوعة، نحتاج إلى وضع ..
في السطر التالي.
--لا
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn" ..
"plugin-limit-conn")
--نعم
return limit_conn_new("plugin-limit-conn" .. "plugin-limit-conn"
.. "plugin-limit-conn")
المتغيرات
تم التأكيد على هذه النقطة عدة مرات في المقالات السابقة: يجب دائمًا استخدام المتغيرات المحلية بدلاً من المتغيرات العامة.
--لا
i = 1
s = "apisix"
--نعم
local i = 1
local s = "apisix"
أما بالنسبة لتسمية المتغيرات، يجب استخدام نمط snake_case
.
--لا
local IndexArr = 1
local str_Name = "apisix"
--نعم
local index_arr = 1
local str_name = "apisix"
بالنسبة للثوابت، يجب استخدام نمط all-caps
.
--لا
local max_int = 65535
local server_name = "apisix"
--نعم
local MAX_INT = 65535
local SERVER_NAME = "apisix"
الجداول
في OpenResty، نستخدم table.new
لتخصيص الجدول مسبقًا.
--لا
local t = {}
for i = 1, 100 do
t[i] = i
end
--نعم
local new_tab = require "table.new"
local t = new_tab(100, 0)
for i = 1, 100 do
t[i] = i
end
أيضًا، لاحظ أنه يجب ألا تستخدم nil
في المصفوفة، وإذا كان يجب عليك استخدام قيمة فارغة، استخدم ngx.null
للإشارة إلى ذلك.
--لا
local t = {1, 2, nil, 3}
--نعم
local t = {1, 2, ngx.null, 3}
السلاسل النصية
لا تقم بدمج السلاسل النصية في مسار الكود الساخن.
--لا
local s = ""
for i = 1, 100000 do
s = s .. "a"
end
--نعم
local t = {}
for i = 1, 100000 do
t[i] = "a"
end
local s = table.concat(t, "")
الدوال
تسمية الدوال تتبع أيضًا نمط snake_case
.
--لا
local function testNginx()
end
--نعم
local function test_nginx()
end
ويجب أن تعود الدالة في أقرب وقت ممكن.
--لا
local function check(age, name)
local ret = true
if age < 20 then
ret = false
end
if name == "a" then
ret = false
end
-- do something else
return ret
--نعم
local function check(age, name)
if age < 20 then
return false
end
if name == "a" then
return false
end
-- do something else
return true
الوحدات
يجب أن تكون جميع المكتبات التي يتم require
ها محلية:
--لا
local function foo()
local ok, err = ngx.timer.at(delay, handler)
end
--نعم
local timer_at = ngx.timer.at
local function foo()
local ok, err = timer_at(delay, handler)
end
للتأكد من تناسق النمط، يجب أيضًا جعل require
و ngx
محليتين:
--لا
local core = require("apisix.core")
local timer_at = ngx.timer.at
local function foo()
local ok, err = timer_at(delay, handler)
end
--نعم
local ngx = ngx
local require = require
local core = require("apisix.core")
local timer_at = ngx.timer.at
local function foo()
local ok, err = timer_at(delay, handler)
end
معالجة الأخطاء
بالنسبة للدوال التي تعود بمعلومات الخطأ، يجب الحكم على معلومات الخطأ ومعالجتها:
--لا
local sock = ngx.socket.tcp()
local ok = sock:connect("www.google.com", 80)
ngx.say("successfully connected to google!")
--نعم
local sock = ngx.socket.tcp()
local ok, err = sock:connect("www.google.com", 80)
if not ok then
ngx.say("failed to connect to google: ", err)
return
end
ngx.say("successfully connected to google!")
وفي حالة الدوال التي تكتبها بنفسك، يجب أن تعود معلومات الخطأ كمعامل ثاني بتنسيق سلسلة نصية:
--لا
local function foo()
local ok, err = func()
if not ok then
return false
end
return true
end
--لا
local function foo()
local ok, err = func()
if not ok then
return false, {msg = err}
end
return true
end
--نعم
local function foo()
local ok, err = func()
if not ok then
return false, "failed to call func(): " .. err
end
return true
end
الخلاصة
هذه نسخة أولية من دليل نمط الترميز، وسنقوم بتوفيرها على GitHub للتحديثات والصيانة المستمرة. نرحب بمشاركتك هذه المواصفات حتى يتمكن المزيد من مستخدمي OpenResty من المشاركة.