تکامل API ها یک مسیر درست - بخش دوم

تکامل api یک موضوع بسیار مهم می باشد، در این مطلب قصد داریم نکاتی بسیار مهم برای توسعه و تکامل api ها را در اختیار شما قرار دهیم.

 تکامل API ها یک مسیر درست - بخش دوم

در بخش قبلی درباره اینکه چرا باید نسبت به توسعه api توجه ویژه ای داشته باشیم صحبت کردیم و اطلاعات جالبی را برای انجام این کار در اختیار گذاشتیم که مهمترین این اطلاعات شامل تعهدهایی بودند که شما نسبت به کاربران api خود داشتید و باید نکات بسیار مهمی را برای به روز رسانی کتابخانه خود رعایت می کردید. در این مطلب قصد داریم ادامه این تعهدها را بیان کنیم و توضیحات بیشتری را درباره این موضوع در اختیار شما قرار دهیم، بنابراین اگر شما نیز جزو آن دسته از افرادی هستید که علاقه مند به ساخت یک کتابخانه می باشید به شما توصیه می کنیم حتما این مطلب را به خوبی مطالعه کنید.
عهد هفتم: انتخاب یک طرح ورژن

به صورت کلی 2 طرح وجود دارد که به صورت گسترده مورد استفاده قرار می گیرد که این دو طرح ورژن بندی معنایی و ورژن بندی بر اساس زمان می باشند. توصیه من به شما استفاده از ورژن بندی معنایی می باشد، flavor thereof پایتون در pep440 تعریف شده است و از ورژن بندی معنایی در آن استفاده شده است. اگر شما این طرح را برای استفاده در کتابخانه و api خود انتخاب کردید شما می توانید به راحتی با استفاده از مراحل زیر پایه های کتابخانه خود را حذف کنید:

First "stable" release, with walk()

1.1: Add slither(), deprecate walk()

2.0: Delete walk()

کاربران api شما باید شامل یک بازه از ورژن های کتابخانه شما باشند، مانند مثال زیر:

# User's requirements.txt.

reptile>=1,<2

این کار به آنها این اجازه را می دهد که به صورت خودکار در هنگام منتشر شدن api شما به روزرسانی شوند، رفع اشکالات و ایرادها را دریافت کرده و برخی از هشدارها نیز برای آنها افزایش پیدا خواهد کرد اما آنها مطمئن خواهند بود که استفاده از این نسخه به روزرسانی شده باعث شکست کد آنها نخواهد شد.

اگر شما روش ورژن بندی بر اساس زمان را استفاده کنید به روز رسانی های شما بر اساس زمان و به شکل زیر شماره گذاری می شوند:

2017.06.0: A release in June 2017

2018.11.0: Add slither(), deprecate walk()

2019.04.0: Delete walk()

و کاربران می توانند به شکل زیر به کتابخانه و api شما وابسته شوند:

# User's requirements.txt for time-based version.

reptile==2018.11

.*

این فوق العاده است، اما چگونه کاربران خود را از نوع طرح بندی ورژن خود مطلع سازید؟ و چگونه کد خود را تست کنید؟ شما باید به آنها آموزش دهید که چگونه api شما را به روز رسانی کنند.

 api


عهد هشتم: برای به روز رسانی خود راهنما بنویسید

در اینجا راهنمایی هایی که یک توسعه دهنده api مسئولیت پذیر باید برای نوشتن به روز رسانی api خود رعایت کند را آورده ایم:
مهاجرت از api های لغو شده:

لاگ تغییرات را برای مشاهده ویژگی های حذف شده مشاهده کنید.
هشدارهای مربوط به ویژگی های لغو شده را فعال کنید

نسخه خود را به نسخه 1.1 به روز رسانی کنید و کد خود را با استفاده از دستور زیر آزمایش کنید:

python -Werror::DeprecationWarning

حال شما می توانید در امنیت کامل به روز رسانی را انجام دهید.

 api


آموزش هایی که باید به کاربران خود ارائه دهید

شما باید به کاربران کتابخانه خود آموزش دهید که چگونه با استفاده از مشاهده گزینه های مختلف دستورات کامند لاین هشدارهای مربوط به تغییرات را مدیریت کنند، بسیاری از برنامه نویسان پایتون این را نمی دانند( من همواره سینتکس ها را در هر زمانی بررسی می کند). شما باید نسخه ای از کتابخانه خود را منتشر کنید که هشدارها را برای api که قرار است حذف شود و یا تغییر یابد را چاپ کند تا کاربران بتوانند با استفاده از این هشدارها دوباره نسخه کد خود را با استفاده از کتابخانه قبلی تست کنند. در این مثال نسخه 1.1 به روز رسانی جدید می باشد، این به کاربران شما این اجازه را می دهد که بتوانند کدهای خود را با استفاده از آن دوباره تست کنند و هر یک از هشدارهای مربوط به ویژگی های حذف شده را به صورت جداگانه اصلاح کنند تا زمانی که بتوانند به صورت کامل به نسخه جدید کتابخانه شما منتقل شوند. آنها می توانند به این شکل تغییرات را در کد خود تست کنند و آنها را بر اساس کتابخانه شما تغییر دهند، همچنین می توانند به صورت مستقل اقدام به فیکس کردن باگ های برنامه خود کنند.

 api


نکاتی مهم درباره آموزش کاربران

اگر شما ورژن بندی معنایی را انتخاب کنید، این دوره تا زمانی که api شما با یک به روز رسانی بزرگ همراه شود به طول می انجامد، منظور از به روز رسانی بزرگ به روز رسانی از 1.x به 2.0 و یا از 2.x به 3.0 می باشد. یک روش آرام و مفید برای حذف کردن برخی از ویژگی های پایه ای این است که به آن حداقل به اندازه یک ورژن فرصت دهید تا کاربران با آن کنار بیایند. به هیچ وجه تمامی ویژگی های پایه ای را به صورت همزمان و یکباره حذف نکنید.

شماره ورژن ها، هشدارهای مربوط به موارد حذف شده، لاگ تغییرات و آموزش به روز رسانی با یکدیگر کار می کنند تا باعث تکامل api شما شوند بدون آنکه کد کاربران شما که از این کتابخانه استفاده کرده اند با شکست روبرو شود. سیاست سازگاری پروژه های پیچیده این زیبایی را به شکل زیباتری توضیح می دهند:

" نسخه اولیه همواره باید رایگان باشد"

هر برنامه ای که بدون باگ اجرا شود می تواند یک نسخه جزئی از پروژه Twisted را به روز رسانی کند.

به عبارت دیگر هر اپلیکیشنی که می تواند تست های خود را بدون آنکه اخطاری را از سمت Twisted دریافت کند اجرا می کند می تواند بدون آنکه هیچ گونه عوارض احتمالی به جز هشدارهای معمولی را ایجاد کند به راحتی پروژه Twisted را به روز رسانی کند.

حال ما توسعه دهندگان api قصد داریم که بتوانیم علاوه بر اضافه کردن و حذف کردن ویژگی ها به راحتی متدها را نیز اضافه و حذف کنیم. ما همچنین می توانیم پارامترهای یک متد را نیز اضافه و کم کنیم، اما این کار می تواند مشکلاتی را برای ما ایجاد کند که در ادامه درباره آن صحبت خواهیم کرد.

 api


اضافه کردن پارامترها

فرض کنید که موجود شبیه به مار شما در حال حاضر دارای یک جفت بال می باشد، حال شما باید انتخاب کنید که این موجود شما با حرکت دادن این بال ها حرکت کند و یا پرواز کند. در حال حاضر متد حرکت شما تنها یک پارامتر را دریافت می کند.

# Your library code.

def move(direction):

    print(f'slither {direction}')


# A user's application.

move('north')

حال شما می خواهید که یک پارامتر حالت نیز به آن اضافه کنید اما اگر این متد را به روز رسانی کنید کدهای کاربران شما با شکست روبرو خواهند شد چرا که متدهای آنها تنها یک پارامتر را به عنوان پارامتر ورودی دریافت می کند.

# Your library code.

def move(direction, mode):

    assert mode in ('slither', 'fly')

    print(f'{mode} {direction}')


# A user's application. Error!

move('north')

یک سازنده api متعهد باید به گونه ای به روز رسانی را انجام دهد که کد کاربران به این طریق با شکست روبرو نشود.

 api

عهد نهم: پارامترها را به صورت سازگار اضافه کنید

برای اینکه بتوانید به این عهد متعهد باشید هر پارامتر جدید را با استفاده از یک مقدار پیش فرض اضافه کنید که متد اصلی نیز به این روش حفظ شود.

# Your library code.

def move(direction, mode='slither'):

    assert mode in ('slither', 'fly')

    print(f'{mode} {direction}')


# A user's application.

move('north')

با گذشت زمان پارامترها در واقع تاریخچه طبیعی تکامل توابع api شما خواهند بود. در ابتدا هر یک از آنها توسط یک مقدار اولیه لیست می شوند، کاربران کتابخانه و api شما می توانند آرگومان های کلیدی را به عنوان آرگومان های اصلی به توابع ارسال کنند و برای حفظ رفتار آنها در کدهای قبلی می توانند به جای سایر آرگومان نیز مقدار پیش فرض را به توابع ارسال کنند.

# Your library code.

def move(direction,

        mode='slither',

        turbo=False,

        extra_sinuous=False,

        hail_lyft=False):

    # ...


# A user's application.

move('north', extra_sinuous=True)

 api

یک خطر مهم

عملیات بالا می تواند یک خطر بسیار مهم باشد چرا که کاربر می تواند آرگومان ها را به شکل زیر به توابع خود اضافه کند:

# A user's application, poorly-written.

move('north', 'slither', False, True)

اگر کاربر به این شکل آرگومان ها را اضافه کند چه اتفاقی می افتد؟ با این کار در نسخه اصلی به روز رسانی آینده شما از یک پارامتر مانند “turbo” خلاص می شوید؟

# Your library code, next major version. "turbo" is deleted.

def move(direction,

        mode='slither',

        extra_sinuous=False,

        hail_lyft=False):

    # ...



# A user's application, poorly-written.

move('north', 'slither', False, True)

 api

نکاتی مهم درباره قطعه کد بالا و مراحل حذف یک پارامتر

کدی که توسط کاربر نوشته شده است همچنان کامپایل می شود که این موضوع می تواند اذیت کننده باشد. به صورت کلی حذف یک پارامتر از توابع api نیازمند چند مرحله می باشد، مرحله اول این است که شما بتوانید پارامتر توربو را خنثی کنید، من برای انجام این کار علاقه مند به تکنیک زیر می باشم که تشخیص می دهد که آیا کدی که توسط کاربر نوشته شده است به این پارامتر وابسته است یا خیر؟

# Your library code.

_turbo_default = object()


def move(direction,

        mode='slither',

        turbo=_turbo_default,

        extra_sinuous=False,

        hail_lyft=False):

    if turbo is not _turbo_default:

       warnings.warn(

           "'turbo' is deprecated",

           DeprecationWarning,

           stacklevel=2)

    else:

       # The old default.

       turbo = False

با این حال کاربران شما ممکن است توجهی به هشدارها نداشته باشند، هشدارها خیلی بلند نیستند، آنها می توانند در فایل های لاگ گم شده و یا نادیده گرفته شوند، کاربران ممکن است بدون اطلاع اقدام به، به روز رسانی api شما کنند که پارامتر توربو را در آن حذف شده است. کد آنها می تواند بدون هیچ گونه خطایی اجرا شود و عملیات اشتباهی را انجام دهد. همانطور که zen پایتون می گوید: " خطاها به هیچ وجه نباید بدون جلب توجه ارسال شوند". بنابراین شما برای اینکه توجه کاربر api خود را جلب کنید باید این خطاها را به صورت واضح اعلام کنید.

 api
بهترین راه برای محافظت از کدهای کاربر

بهترین راه برای اینکه بتوانید از کدهای کاربران خود محافظت کنید این است که از استار سینتکس پایتون نسخه 3 استفاده کنید، این سینتکس به شما کمک می کند تا بتوانید فراخوانی هایی که تنها آرگومان های کلیدی را ارسال می کنند را مورد استفاده قرار دهید:

# Your library code.

# All arguments after "*" must be passed by keyword.

def move(direction,

        *,

        mode='slither',

        turbo=False,

        extra_sinuous=False,

        hail_lyft=False):

    # ...


# A user's application, poorly-written.

# Error! Can't use positional args, keyword args required.

move('north', 'slither', False, True)

با استفاده از استار این کد تنها سینتکس مجاز می باشد:

# A user's application.

move('north', extra_sinuous=True)

حال زمانی که شما توربو را حذف می کنید، می توانید مطمئن باشید که کد کاربری که به api شما وابسته است به هیچ وجه با شکست روبرو نخواهد شد. اگر کتابخانه شما پایتون نسخه 2 را نیز پشتیبانی می کند شما می توانید به راحتی این کار را با استفاده از دستورات زیر شبیه سازی کنید:

# Your library code, Python 2 compatible.

def move(direction, **kwargs):

    mode = kwargs.pop('mode', 'slither')

    turbo = kwargs.pop('turbo', False)

    sinuous = kwargs.pop('extra_sinuous', False)

    lyft = kwargs.pop('hail_lyft', False)


    if kwargs:

       raise TypeError('Unexpected kwargs: %r'

                       % kwargs)


    # ...

 api

نکات پایانی درباره عهد نهم

فراهم کردن آرگومان های کلیدی یک انتخاب بسیار هوشمندانه می باشد، اگر شما به یک آرگومان اجازه دهید که به موقع ارسال شود شما نمی توانید به آن اجازه دهید که در نسخه بعدی کتابخانه شما تبدیل به یک آرگومان کلیدی شود، بنابراین نیاز دارید که استار را در اینجا اضافه کنید. شما می توانید api asyncio را مشاهده کنید که به صورت فراگیر از استار استفاده کرده است.

# In asyncio.

class Lock:

    def __init__(self, *, loop=None):

       # ...

حال ما به سطحی از دانش دست پیدا کرده ایم که می توانیم متدها و آرگومان های ورودی آنها را به گونه ای تغییر دهیم که سازگاری آن با کدهای کاربر بهم نریزد. حال زمان آن فرا رسیده است که بزرگترین چالش در تکامل کتابخانه را مورد بررسی قرار دهیم که این چالش تغییر رفتارهای کتابخانه ما بدون تغییر متدها و آرگومان های آنها می باشد.

 api
تغییر رفتار کتابخانه

فرض کنید که قصد دارید برای کتابخانه خود یک رفتار جدید ایجاد کنید و یا اینکه قصد دارید یکی از رفتارهای api را مورد تغییر قرار دهید. فرض کنید یک مار دارید که قصد دارید به او یک رفتار جدید بیاموزید. به مار خود پاهایی را اضافه می کنید اما چگونه می خواهید به او آموزش دهید که پیاده روی کند؟ چگونه می توانیم مار را برای این تکامل آماده کنیم؟

یک خالق مسئولیت پذیر می تواند نکاتی را از مثال زیر که در کتابخانه استاندارد پایتون وجود دارد بیاموزد، زمانی که یک رفتار بدون تغییر در یک تابع و آرگومان های آن و یا حتی تعریف یک تابع جدید تغییر می کند تابع os.stat اجرا می شود تا آمار مربوط به فایل مورد نظر را معرفی کند، در ابتدا زمان ها همواره از نوع integer می باشند.

>>> os.stat('file.txt').st_ctime

1540817862

 api

نکاتی مهم درباره تغییر رفتار api

روزی توسعه دهندگان اصلی این api تصمیم گرفتند که از زمان هایی از نوع float برای این تابع استفاده کنند تا دقت بیشتری را بدست آورند، اما آنها همواره این نگرانی را داشتند که کد کاربران این api با استفاده کردن از این نوع داده ای با شکست روبرو شود، آنها تغییراتی را در پایتون 2.3 به وجود آوردند که به شکل "stat_float_times," بود و به صورت پیش فرض آن را false انتخاب کردند، یک کاربر می تواند آن را بر روی true تنظیم کند تا در زمان های خود بهینه سازی لازم را انجام دهد.

>>> # Python 2.3.

>>> os.stat_float_times(True)

>>> os.stat('file.txt').st_ctime

1540817862.598021

با شروع پایتون نسخه 2.5 زمان هایی از نوع float به صورت پیشفرض تعریف شدند، بنابراین هر کد جدیدی که در api پایتون 2.5 نوشته می شود این تنظیمات را نادیده می گیرد و زمان ها را به صورت float نمایش می دهد. البته شما در این نسخه از پایتون نیز می توانید این ویژگی را بر روی false تنظیم کنید تا از رفتارهای قدیمی استفاده کنید و یا می توانید آن را بر روی true  تنظیم کنید تا api شما بر روی تمامی نسخه های پایتون اجرا شود.

با گذر زمان در پایتون نسخه 3.1 این تنظیمات نادیده گرفته می شد تا تمامی استفاده کنندگان از این api برای استفاده از ویژگی های نهایی آماده شوند، بعد از گذشت مدت زمانی این ویژگی به صورت کامل حذف شد. زمان های از نوع float در حال حاضر تنها به صورت اختیاری مورد استفاده قرار می گیرند. این یک مسیر طولانی است، اما سازندگان api باید صبر کافی برای انجام این کار را داشته باشند، چرا که این روند تدریجی می تواند باعث موفقیت چند برابری api شما در آینده ای نزدیک شود.

 api


عهد دهم: رفتارها را به تدریج تغییر دهید

شما برای انجام این کار مراحل زیر را باید بپیمایید:

-          یک flag برای بهینه کردن به رفتار جدید بیفزایید که حالت پیشفرض آن false می باشد و در صورت false بودن هشدار دهد.

-          حالت پیشفرض را به true تغییر دهید و به صورت کامل flag را از بین ببرید.

-          پرچم را حذف کنید

-          اگر شما از ورژن بندی معنایی استفاده می کنید ورژن api شما می تواند به شکل زیر باشد:

Library version                      Library API                                  User code

1.0                                      No flag                                      Expect old behavior

1.1              Add flag, default False,warn if it's False              Set flag True,handle new behavior

2.0              Change default to True,deprecate flag entirely  Handle new behavior

3.0                                      Remove flag                                 Handle new behavior

شما نیاز به دو انتشار برای کامل کردن این عملیات دارید، اگر شما به صورت مستقیم از " flag را به صورت پیشفرض false اضافه کن و اگر false بود هشدار بده" استفاده می کنید برای حذف کردن flag بدون مشکل در نسخه api خود از کد کاربر شما برای به روز رسانی باید غیرفعال شود. اگر کد کاربر به درستی در نسخه 1.1 از api شما نوشته شده است که flag را مساوی true قرار می دهد و رفتار جدید را مدیریت می کند باید بتواند نسخه بعدی از api شما را به روز رسانی کند و بتواند هشدارهای جدیدی را به وجود آورد.

 api


محتاط بودن در ساخت یک api

ویژگی ها و عهدهایی که در این دسته قرار می گیرند شامل موارد زیر می باشند:

-          جلوگیری از اضافه کردن ویژگی های بد

-          کاهش تعداد ویژگی ها

-          ویژگی ها را محدود نگه دارید

-          ویژگی های آزمایشی را با عبارت موقت برچسب گذاری کنید

-          ویژگی ها را به آرامی حذف کنید


ویژگی های مربوط به ثبت تاریخ به صورت دقیقا در api

-          نگهداری لاگ تغییرات

-          انتخاب یک طرح ورژن

-          نوشتن یک راهنمای آپدیت api

 api


به روز رسانی api در نهایت آرامش

-          پارامترها را به صورت سازگار به api خود اضافه کنید تا api در به روز رسانی دچار مشکل نشود

-          رفتارهای کتابخانه خود را به صورت تدریجی تغییر دهید تا بتوانید با موفقیت و بدون نیاز به تغییر متدها آنها را تغییر دهید.