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

تکامل و به روزرسانی api یک مسئله بسیار مهم می باشد، ما در این مطلب قصد داریم نکات بسیار مهمی را برای به روز رسانی api شما بیان کنیم

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

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

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


مقدمه ای درباره این موضوع

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

 api


اضافه کردن ویژگی های مفید

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

class Reptile:

   @property

   def teeth(self):

      return 'sharp fangs'

   # اگر بال ها می توانند مفید باشند آنها را اضافه کن

 @property

   def wings(self):

      return 'majestic wings'

اما مراقب باشید چرا که اضافه کردن ویژگی ها می تواند بسیار خطرناک باشد، ویژگی های زیر را در یک کتابخانه استاندارد پایتون در نظر بگیرید و مشاهده کنید که چه اتفاقی می افتد:

bool(datetime.time(9, 30)) == True

bool(datetime.time(0, 0)) == False

این عجیب است، تبدیل هر شی زمان به یک متغیر بولین درست به جز نیمه شب ها( بدتر از همه قوانین مربوط به روزها پیچیده تر از این است).

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

یک برنامه تقویم که با استفاده از یک تابع وقایع را ایجاد می کند را در نظر بگیرید، اگر یک رویداد یک زمان انتهایی داشته باشد تابع به آن نیاز دارد که زمان شروع را نیز دریافت کند.

def create_event(day,

               start_time=None,

               end_time=None):

   if end_time and not start_time:

      raise ValueError

("نقطه پایان را بدون نقطه شروع نمی توان به تابع ارسال کرد")

# قاچاقچیان از نیمه شب تا ساعت 4 صبح می آیند

create_event(datetime.date.today(),

           datetime.time(0, 0),

           datetime.time(4, 0))

 api


نکاتی دیگر برای اضافه کردن ویژگی های جدید به api

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

def create_event(day,

               start_time=None,

               end_time=None):

   if end_time is not None and start_time is None:

      raise ValueError

("زمان پایان تابع را نمی تواند بدون دانستن زمان شروع ارسال کند")

اما این ظرافت می تواند نگران کننده باشد، اگر سازنده یک کتابخانه بخواهد که یک api ایجاد کند که کاربران را مورد حمله قرار می دهد یک متغیر بولین نیمه شب می تواند برای این کار بسیار مفید باشد.

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

این ویژگی توسط Tim Peters زمانی که اولین ماژول datetime را در سال 2002 ایجاد کرد نوشته شده است، حتی برنامه نویسان پایتون مانند Tim نیز اشتباهاتی را انجام می دهند، حالات تکراری حذف شد و الان تمامی متغیرهای زمان true هستند.

Python 3.5 and later.

bool(datetime.time(9, 30)) == True

bool(datetime.time(0, 0)) == True

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

 api


وعده اول: اجتناب از استفاده از ویژگی های بد

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

عکس 4


وعده دوم: کاهش دادن ویژگی ها تا جای ممکن

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

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

 api


یک داستان احمقانه از asyncio

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

async def my_coroutine():

   pass

print(my_coroutine())

<coroutine object my_coroutine at 0x10bfcbac8>

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

زمانی که Yury Selivanov حالت خطایابی را پیاده سازی کرد او پایه و اساس کار خود را براساس یک ویژگی "coroutine wrapper" ایجاد کرد.

Wrapper یک تابع است که یک شی coroutine را دریافت می کند و هر آن چه که در آن قرار دارد را باز می گرداند، یوری از آن استفاده کرد تا منطق خطایابی خود را پیاده سازی کند، اما شخص دیگری می تواند از آن استفاده کند تا شی coroutine را در قالب یک استرینگ یا رشته با مقدار “hi” باز گرداند.

import sys

def my_wrapper(coro):

   return 'hi!'

sys.set_coroutine_wrapper(my_wrapper)

async def my_coroutine():

   pass

print(my_coroutine())

این تنها بخشی از دنیای شخصی سازی می باشد، این موضوع معنای async را به صورت کامل تغییر می دهد، فراخوانی set coroutine wrapper برای یک بار می تواند به صورت کلی تمامی توابع coroutine را تغییر دهد. ناتانیل اسمیت درباره این موضوع نوشته است که: " یک api مشکل ساز" است که مستعد سوء استفاده باشد و نیاز به حذف آن احساس شود. توسعه دهندگان asyncio می توانند از مشکلات مربوط به حذف ویژگی ها رها شوند اگر بتوانند هدف خود را به شکل بهتری انتخاب و طراحی کنند. در واقع سازندگان باید نکاتی که در ادامه مطلب بیان می کنیم را همواره در ذهن خود داشته باشند.

 api


عهد سوم: ویژگی های api را محدود نگه دارید

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

import sys

sys.set_coroutine_origin_tracking_depth(2)

async def my_coroutine():

   pass

print(my_coroutine())

نتیجه:

<coroutine object my_coroutine at 0x10bfcbac8>

RuntimeWarning:'my_coroutine' was never awaited

Coroutine created at (most recent call last)

File "script.py", line 8, in <module>

   

print(my_coroutine())

این خیلی بهتر است، هیچ تنظیمات کلی دیگری وجود ندارد که بتواند شی coroutine را تغییر دهد، بنابراین کاربران asyncio نیازی به داشتن کد ندارند.

 api


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

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

 api


حذف ویژگی ها

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

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

به طور مشابه کتابخانه استاندارد پایتون برای پاسخگویی به تغییراتی که در این زبان برنامه نویسی به وجود می آید برخی از ویژگی ها را حذف می کند، قفل asyncio را در نظر بگیرید، از زمانی که await به عنوان یک کلمه کلیدی به آن اضافه شده است حالت انتظار را دارا می باشد:

lock = asyncio.Lock()

async def critical_section():

   await lock

   try:

      print('holding lock')

   finally:

      lock.release()

اما در حال حاضر ما async را با استفاده از lock انجام می دهیم.

lock = asyncio.Lock()

async def critical_section():

   async with lock:

     

 print('holding lock')

این سبک جدید بسیار بهتر است چرا که بسیار کوتاه است و کمتر در معرض خطا و ارور قرار می گیرد، از آن جایی که تنها یک روش آشکار برای انجام این کار باید وجود داشته باشد سینتکس قدیمی در پایتون نسخه 3.7 ضعیف شده است و به زودی نیز ممنوع خواهد شد.

 api


نکاتی مهم درباره حذف ویژگی های api

این موضوع اجتناب ناپذیر است که تغییراتی که در api شما به وجود می آیند بر روی کدهای شما تاثیر گذار هستند، بنابراین شما باید یاد بگیرید که ویژگی های api خود را به تدریج حذف کنید، قبل از انجام این کار هزینه و زیان حذف یک ویژگی api خود را بررسی کنید، نگهدارنده های api تمایلی ندارند که به میزان زیادی کدهای api خود را کاربران تغییر دهند و یا این که به صورت کلی منطق برنامه خود را تغییر دهند، به یاد داشته باشید که پایتون 3 قبل از آن که پیشوند رشته “u” را تغییر دهد back را اضافه کرده بود، اگر کدهای در واقع مهندسی شده باشند مانند یک الگوریتم جست و جوی ساده حذف آنها می تواند راحت باشد، اما در صورتی که یک ویژگی خطرناک و با ارزش باشد می تواند هزینه زیادی را برای شما داشته باشد.

 api


یک ویژگی را حذف کنیم یا خیر؟

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

class Reptile:

   def walk(self):

      print('step step step')

به متد زیر تبدیل کنیم:

class Reptile:

   def slither(self):

      
print('slide slide slide')

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

# User's code. Oops!

Reptile.walk()

بنابراین سازندگان api باید تعهد زیر را داشته باشند:

 api


تعهد پنجم: ویژگی ها را به آرامی حذف کنید

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

import warnings


class Reptile:

   def walk(self):

      warnings.warn(

          "walk is deprecated, use slither",

          DeprecationWarning, stacklevel=2)

      print('step step step')


   def slither(self):

      
print('slide slide slide')

 api


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

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

Reptile().walk()

 api


چه اتفاقی می افتد اگر آنها کد خود را با api به روزرسانی شده اجرا کنند؟

$ python3 script.py


DeprecationWarning: walk is deprecated, use slither

script.py:14: Reptile().walk()


step step step

به صورت پیشفرض آنها هشداری را در stderr مشاهده می کنند اما اسکریپت با موفقیت اجرا و چاپ می شود: “ step step step”. در این مرحله هشدار نشان می دهد که کدام خط از کدهای کاربر باید اصلاح شود. توجه داشته باشید که این ارور می تواند برای کاربر api بسیار آموزنده باشد چرا که به صورت دقیق توصیف می کند که کاربر api برای رفع این مشکل باید چه کاری را انجام دهد.

 api


تست کدهای جدید

کاربران شما می خواهند که کدهای خود را تست کنند و اثبات کنند که کدهای اشتباهی را از api شما استفاده نکرده اند، هشدارها به تنهایی نمی توانند باعث شوند که یونیت تست ها با شکست رو به رو شوند، پایتون یک دستور برای تبدیل کردن هشدارها به ارور دارا می باشد.

> python3 -Werror::DeprecationWarning script.py


Traceback (most recent call last):

File "script.py", line 14, in <module>

   Reptile().walk()

File "script.py", line 8, in walk

   DeprecationWarning, stacklevel=2)

DeprecationWarning: walk is deprecated, use slither

البته توجه داشته باشید که “step step step” الان چاپ نمی  شود چرا که اسکریپت با یک خطا به پایان می رسد، بنابراین اگر شما یک نسخه از api خود را منتشر کرده اید که نسبت به متد walk هشدار می دهد، شما می توانید آن را در نسخه بعدی با خیالی راحت حذف کنید.

حتما به این موضوع نیز توجه داشته باشید که کاربران api شما برای پروژه خود چه نیازمندی هایی را می خواهند.

# User's requirements.txt has a dependency on the reptile package.

Reptile

 api


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

کاربران api شما دفعه بعد که کد خود را اعمال می کنند آخرین نسخه از کتابخانه شما را نصب خواهند کرد، اگر آنها همچنان این مورد را نادیده می گیرند بنابراین کد آنها در آینده با شکست رو به رو خواهد شد چرا که این پروژه هنوز هم به متد walk وابسته است، شما باید برای انجام این کار آرامش زیادی داشته باشید. سه وعده دیگر نیز وجود دارد که شما باید نسبت به آنها برای کاربران خود تعهد داشته باشید. حفظ لاگ های تغییرات، انتخاب یک version scheme و نوشتن یک راهنمای برای آپدیت و به روز رسانی خود.

 api


عهد ششم: نگهداری لاگ تغییرات api

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

Changes in Version 1.1

New features


New function Reptile.slither()

Deprecations


Reptile.walk() is deprecated and will be removed in version 2.0, use slither()

سازندگان کتابخانه ها که مسئولیت پذیر هستند از شماره ورژن استفاده می کنند تا بیان کنند که یک کتابخانه چگونه تغییر پیدا خواهد کرد و به روزرسانی می شود، بنابراین کاربرانی که از این کتابخانه ها استفاده می کنند می توانند یک تصمیم آگاهانه درباره این به روزرسانی بگیرند. یک version scheme یا طرح ورژن برای برقراری ارتباط بین کاربران و سرعت به روزرسانی است که در ادامه مطلب بیشتر با آن و کاربردهای آن آشنا خواهیم شد.

 api