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

اسم من Florian Deißenböck است. همانطور که متوجه شدید این اسم شامل دو کاراکتر ß و ö می شود که جزو حروف ساده لاتین نیستند. با اینکه کاراکترهای ساده لاتین نیستند ولی این کاراکترها در زبان آلمانی کاملا رایج هستند.

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

encoding problem examples

لطفا دقت کنید که دو شرکتی که این ایمیلها را فرستاده اند هردو آلمانی هستند پس یک اسم با یک sharp s و o-omlaut نباید چیز عجیبی برای آنها باشد.

پس حدس بزنید، این ایمیلها توسط نرم افزارهایی ایجاد شده اند ( انسانها معمولا اسم مرا درست می نویسند - حداقل در کشورهای آلمانی زبان) که توسط افراد ناآشنا به انکدینگ و یا با کتابخانه های معیوب نوشته شده اند. این نظریه برای من به عنوان یک تحلیلگر کیفیت نرم افزار اثبات شده است: تقریبا در هر سیستمی که ما کنترل میکنیم متوجه میشویم که کمبودهایی در مبحث متن وجود دارد. این موضوع در وحله معماری همانند وحله کد وجود دارد (اکثر مستندات معماری حتی صحبتی درمورد این موضوع نمیکنند).

من از خودم پرسیدم که این مشکلات چرا باید هنوز هم که ما در سال 2015 هستیم رایج باشد. به هر حال انکدینگ ASCII پنجاه و دو سال پیش منتشر شده است و یونیکد از 25 سال پیش آمده است. مطالب بسیار زیادی در اینترنت و کتابفروشی ها در مورد هندل کردن صحیح متن وجود دارد و بسیاری از زبانهای برنامه نویسی مدرن کمابیش پیاده سازی درستی از مدیریت کارکترها دارند. حداقل بعد از اینکه joel splsky مقاله مشهور خود The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets را منتشر کرد دیگر عذری برای نادیده گرفتن این مطلب نمی ماند. هرچند اکثر برنامه نویسان هنوز راه حلهای ابتکاری خودشان را برای این کار به یاد می آورند. بنظر من اینها علل اصلی این نادیده گرفتن هستند :

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

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

کمی پیش نیاز

وقتی در مورد کارکترها صحبت میکنیم، بنظرم بهتر است مفاهیم زیر را از هم متمایز کنیم. مثلا وقتی شما در مورد حرف A صحبت میکنید باید مفاهیم زیر را ازهم جدا کنید:

  • گلیف (glyph): ارایه بصری (قابل دیدن) از یک کارکتر مثلا روی کاغذ یا صفحه نمایش است. این نمایش بوسیله فونتی که از آن استفاده میکنید مشخص میشود.
  • نقطه کد (code point): یک مقدار عددی است که کارکتر را نشان میدهد. به عنوان مثال کد حرف A در اکثر کارکترستها عدد 65 می باشد. این کد به مجموعه کارکتر (character set) ای که استفاده میکنید بستگی دارد مثلا US-ASCII و یا ISO-8859-1 و یا یونیکد. در حالیکه حرف مثال ما A کد یکسانی در اکثر کارکترستها دارد، اما این برای دیگر کاراکتر مثل زبان ژاپنی صدق نمیکند.
  • واحد کد (code unit): نمایش دودویی از نقطه کد است، مثلا 01000001. فرمت نمایش باینری بوسیله اسکیمای انکدینگی (encoding scheme) که استفاده میکنید مشخص میشود. در حالیکه کارکترستهایی مثل ISO 8859-1 اسکیمای انکدینگ سرراستی دارند که از یک بایت برای نمایش هر کدیونیت استفاده میکنند، یونیکد از اسکماهای متفاوتی برای نگاشت از کدپوینت به کدیونیت استفاده میکند. مثلا اسکیمای 8 بیتی UTF-8 کدپوینت 65 را به 01000001 نگاشت میکند در حالیکه اسکیمای UTF-16BE و UTF-16LE همان کدپوینت را به ترتیب به 0000000001000001 و 0100000100000000 نگاشت میکنند.

این اصطلاحات و رابطه آنها در شکل زیر نمایش داده شده است. مثال یک نشان میدهد که تا زمانیکه از یک کارکترست و انکدینگ یکسان استفاده میکنید کد پوینت و کدیونیت یکسان هستند و فرقی نمیکند که از چه فونتی استفاده کنید. مثال دوم نشان میدهد که یونیکد و ISO 8859-1 کدپوینتهای مشترک بسیاری دارند حتی برای کاراکترهای نا آشنایی مثل u-umlaut. همچنین نشان میدهد که اسکیمای انکدینگ UTF-8 کدیونیت متفاوتی از اسکیمای سرراست انکدینگ هر بایت یک کدیونیت را تعریف میکند. مثال 3 نشان میدهد که علامت کپی رایت دارای دو کدیونیت متفاوت در دو کارکترست ISO 8859-1 و IBM 850 می باشد.

encoding terms

در بسیاری از موارد استفاده از یک اسکیمای انکدینگ خاص به معنی استفاده از یک کارکترست بخصوص است. مثلا استفاده از UTF-8 و یا UTF-16 به این معنی است که از Unicode استفاده میشود. در طرف دیگر، کارکترستهای ساده ای مانند ISO 8859-1 که شامل 256 کاکتر یا کمتر میشوند فقط اسکیمای ساده و سرراست هر بایت یک کدیونیت را میشناسند. بنابراین این تمایز مفهومی مهم اغلب بصورت موکد بیان نمیشود. در عوض ترکیب یک کارکترست و یک اسکیمای انکدینگ اغلب باهم به عنوان encoding و حتی گیج کننده تر گاهی به عنوان character set در نظر گرفته می شود.

تک قاعده مهم

با این پیش زمینه کوچک (خیلی ساده شده و شاید کمی اشتباه) مشخص میشود که چرا splosky کاملا محق است که کل مبحث پیچیده و بزرگ هندلینگ کاراکترها را به یک قاعده ( چیزی بنام plain text وجود ندارد) تقلیل دهد. بدون دانستن کارکترست و اسکیما نمیتوانید براحتی یک آرایه از بایتها را به متن تبدیل کنید( یا متن را به آرایه از بایتها ). این تنها چیزی است که باید بدانید!

اگر کسی به شما گفت که فایلهای گزارش در سیستم آنها بصورت plain text هستند، باید از او بپرسید شما از چه کارکترست و چه اسکمیای انکدینگی استفاده میکنید؟ همانطور که پیشتر گفته شد، این سوال را بصورت خلاصه تر میتوانید بپرسید: از چه انکدینگی استفاده میکنید؟ وقتی جوابی مثل UTF-8 میشنوید این به شما هم اسکیمای انکدینگ و هم کارکترست را می دهد. اگر شخصی به شما گفت که میتوانید از API او با ارسال داده بصورت plain text استفاده کنید شما می پرسید: کدام انکدینگ؟ وقتی در حال مرور قطعه کدی هستید و متوجه میشود که یک آرایه از بایت را به متن تبدیل میکند بدون اینکه انکدینگ را بداند، از نویسنده آن بخواهید توضیح دهد که دارد چکار میکند ( و انتشار نسخه بعدی نرم افزار را تا یک هفته به عقب بیاندازید). در واقع هرگاه نیاز باشد که آرایه ای از بایتها به متن تبدیل شود شما باید بپرسید با کدام انکدینگ؟ اگر تنها جوابی که میگیرید یک نگاه خیره باشد بدانید که مشکلاتتان شروع شده است!

چرا خیلی از برنامه نویسان اهمیتی نمی دهند؟

اگر این قاعده این اندازه مهم ( و ساده ) است چرا خیلی از برنامه نویسان آن را رعایت نمیکنند؟ اولا اینکه دو انکدینگ رایج ISO-8859-1 و UTF-8 به اندازه کافی شبیه هستند که برای حروف ساده ای مثل a, b و c و بسیاری از کارکترهای نگارشی مثل ( , ; " ' ) یکسان عمل کنند و تا زمانی که متن مورد استفاده شما شامل حروفی مثل o-umlaut نباشد مشکلی پیش نمی آید. هرچند ممکن است اگر متنتان شامل چنین حروفی باشد کارها خراب شود. مثلا اسم من در اکثر ویرایشگرهای متن اگر با انکدینگ ISO-8859-1 ذخیره شود و بصورت UTF-8 خوانده شود بصورت Dei?enb?ck نمایش داده میشود. این مورد برای شما آشنا نیست؟ به یاد داشته باشید که علامت سوال گلیف اصلی نیست که برای کارکتر خراب شده بکار میرود بلکه راهی است که ادیتور به شما علامت می دهد که مشکلی وجود دارد. به عنوان مثل word آن را بصورت زیر نشان میدهد:

replacement character

دوما بسیاری از زبانهای برنامه نویسی (و کتابخانهای کلاس آنها) تنبلی در این مورد را بسیار راحت میکنند. مثلا جاوا به شما اجازه میدهد که یک شی string از یک آرایه بایتی را با استفاده از متد سازنده ساده String(byte[] bytes) بسازید. اگر قاعده بالا صحیح باشد، این نباید اتفاق می افتاد. چگونه میتوان یک شی string ساخت بدون اینکه انکدینگ را بدانیم؟ جواب در مستندات متد سازنده آمده است : یک استرینگ جدید  از دیکد کردن آرایه مشخص شده با استفاده از کارکترست پیشفرض پلتفرم می سازد. پس اتفاقی که می افتد این است که ماشین مجازی جاوا فرض میکند شما از انکدینگ پیش فرض هر پلتفرم استفاده میکند مثلا UTF-8 در لینوکس و Windows-1252 در اکثر پلتفرمهای ویندوزی و از آن برای دیکدکردن آرایه بایتی استفاده میکند. لازم نیست نابغه باشید تا فهیمده باشید که اگر ما متنی را با این متد در لینوکس بسازیم و ذخیره کنیم و سپس در ویندوز بازکنیم به در کارکترهای غیر لاتین با مشکل مواجه خواهیم شد.

شما باید چکارکنید؟

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

در نهایت در نظر داشته باشید که این پست فقط در مورد مهمترین قاعده بود. برای کارکترها را به بهترین نحو مدیریت کنید شاید نیاز باشدکه بیشتر یاد بگیرید. مراجع زیر میتوانند مکانهای مناسبی برای شروع باشند:

منبع :No Such Thing As Plain Text