انتقل إلى المحتوى

ابحث في CooLWJ

选择搜索范围,输入关键词后按回车。

اللغة

هل الطول المفرط خطأ أيضًا؟——رحلة إصلاح عجيبة لفقدان دقّة معرّفات Long في الواجهة الخلفية

مقدمة

في عصر فصل الواجهة الأمامية عن الخلفية، تمتلئ حياتنا بفرص وتحديات لا تُحصى——ومنها تلك الأخطاء البرمجية التي تظهر فجأة وتكاد تفقدنا صوابنا. وما سنتحدث عنه اليوم هو مشكلة كلاسيكية جعلت عددًا لا يُحصى من المطوّرين حائرين بين الضحك والبكاء:الواجهة الخلفية Long طول معرّف النوع المفرط يؤدي إلى فقدان الدقة في الواجهة الأمامية。أما هذه المسألة، فهي حقًا «أصل كل الشرور»، والسبب أن JavaScript لا يستطيع التعامل بأمان إلا مع Number.MAX_SAFE_INTEGER(أي 9007199254740991)ضمن هذا الحد؟

إذا سبق أن أرهقت تفكيرك في هذه المشكلة، فلا تقلق، فلست وحدك. اليوم، لن نحلّل هذا «اللغز المستعصي عبر العصور» بروح فكاهية فحسب، بل سنرشدك أيضًا إلى حلّه من جذوره، كي لا تعود شيفرتك «تفقد دقّتها».

خلفية المشكلة: لماذا Long كل هذا الطول؟

أولًا، علينا أن نبدأ من Long لنبدأ بالأنواع.Long، وهو Java نوع الأعداد الصحيحة ذي 64 بت فيه، ولمن يحب التعامل مع البيانات الضخمة، والأعداد الكبيرة في Java، فإن هذا النوع نعمة حقًا. لكن عالم الواجهات الأمامية يبدو «محدود الاطّلاع» بعض الشيء، إذ لا يعرف سوى التعامل مع الأعداد الصحيحة التي لا تتجاوز 53 بتًا. نعم، لم تخطئ السماع، ففي هذه النقطة JavaScript إنه أشبه بعالم عجوز «متأخر بنصف خطوة»؛ حين يواجه أعدادًا أكبر، يبدأ بالتلاعب بالفاصلة العشرية والأسس، ثم يلفظ في النهاية رقمًا يصيبك باليأس. وهكذا تبدأ مشكلة فقدان الدقة بالتجول في مشروعك كالشبح.

اللقاء الأول:حكايات فقدان الدقة

لا بدّ أن تبدأ الحكاية من بلاغ عن خلل ورد ذات يوم:「يا أخي المطوّر، انظر، كيف تغيّر شكل هذا المعرّف؟ أليس هذا ما قاعدة البيانات ذلك المعرّف ID الموجود فيه!” تنظر عابسًا، فتجد أن الأمر كذلك فعلًا،9223372036854775807 تحوّل إلى 9223372036854776000، يا للعجب، هذه الأرقام الزائدة تبدو كأنها سحر. فالبيانات التي أرسلتها الواجهة الخلفية صحيحة بالتأكيد! تحكّ رأسك وتقول في نفسك:“لا بد أن الخطأ من الواجهة الأمامية!”

هل المسؤولية على الواجهة الأمامية أم الخلفية؟——تتبّع أصل المشكلة

في الواقع، ليس من غير المعقول تمامًا إلقاء اللوم في هذه المشكلة على الواجهة الأمامية. دعونا نلقِ نظرة على JavaScript “نقطة الضعف” في التعامل مع الأرقام.JavaScript الخاص بـ Number النوع هو صيغة فاصلة عائمة مزدوجة الدقة تستند إلى معيار IEEE 754، ولا يمكنه تمثيل سوى 53 بتًا من الأرقام الثنائية بأمان، أي Number.MAX_SAFE_INTEGER وقيمته——9007199254740991.

بعبارة أخرى، الأعداد الصحيحة التي تتجاوز هذا النطاق،JavaScript سيبدأ حينها «بالتنازل عن الدقة»، إذ تصبح «سعة دماغه» غير كافية فجأة. وعندئذ، فإن Long تصبح معرّفات النوع في نظره مجرد مجموعة من القيم الرقمية الأقل «جاذبية». ولهذا السبب، تضطر الواجهة الأمامية عند معالجة هذه المعرّفات الطويلة بصورة مبالغ فيها إلى تقديم بعض «التضحيات»، والنتيجة أن معرّفك الذي كان أمينًا للأصل يُقتطع ويتحول إلى رقم جديد «طريف».

من الصدمة إلى الهدوء:البحث عن حل

بما أننا عثرنا على أصل المشكلة، فقد حان دورنا نحن المطوّرين لإظهار مهاراتنا. سأصطحبك فيما يلي للتعرّف بعمق إلى عدة حلول، وأشرح لك مزايا كلّ حل وعيوبه، فجميع الطرق تؤدّي إلى روما في النهاية.

1. التحويل مباشرة إلى سلسلة نصية:حل بسيط ومباشر لكنه فعّال

عند مواجهة مشكلة كهذه، فإن أول ما يخطر ببالنا بالتأكيد هو أبسط الأساليب وأكثرها مباشرةً: أن نأخذ مباشرةً Long من النوع تحويل البيانات ألن يكفي تحويله إلى سلسلة نصية! بما أن JavaScript متعصب للأعداد ذات الفاصلة العائمة، فلنعد المشكلة إليه ببساطة ونقل له:“كل ما عليك هو اعتبار هذا سلسلة نصية، ولا تقلق بشأن طولها!”

كيف نحقق ذلك؟ الأمر بسيط في الواقع، باستخدام Jackson الذي يقدّمه ToStringSerializer، يمكننا بسهولة أن نحوّل Long تحويله إلى سلسلة نصية.

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;

public class UserDto {
    @JsonSerialize(using = ToStringSerializer.class)
    private Long id;
    // 其他字段
}


وبذلك، عندما تُرجع الواجهة الخلفية UserDto عندما،Long من النوع id سيُسلسَل إلى سلسلة نصية، وما تستقبله الواجهة الأمامية سيكون سلسلة نصية أيضًا، فتُحل مشكلة الدقة بسهولة. وكأنك تلصق ورقة صغيرة على رأس JavaScript تقول:“هذه سلسلة نصية، فلا تشغل بالك بها!”

2. استخدام الواجهة الأمامية BigInt:تمكين إجراء عمليات حسابية دقيقة حتى على الأعداد الكبيرة

بالطبع، لا تناسب الطريقة المباشرة والبسيطة جميع الحالات دائمًا. تخيلوا أن الواجهة الأمامية تحتاج إلى إجراء عملية حسابية ما على هذا المعرّف ID؛ عندها لن يكون تحويله مباشرةً إلى سلسلة نصية خيارًا جيدًا. إذن، كيف تتعامل الواجهة الأمامية مع هذه المعرّفات «الطويلة جدًا»؟

لحسن الحظ،JavaScript وليس خاليًا من الفوائد تمامًا. فقد أدخل BigInt بعد ذلك،JavaScript وأخيرًا، لم يعد ذلك الأبله الذي لا يجيد سوى العبث بالفاصلة العشرية.BigInt هو نوع بيانات بدائي جديد، مخصّص للتعامل مع الأعداد الصحيحة ذات الدقّة الاعتباطية. يمكنك فعل ما يلي:

const id = BigInt("9223372036854775807");
console.log(id + 1n); // 输出:9223372036854775808n

وبهذه الطريقة، يمكنك معالجة ما يلي بدقة في الواجهة الأمامية Long من هذا النوع من البيانات، لتجنّب فقدان الدقّة. وبالطبع، هناك تنبيه صغير هنا:BigInt ليست جميع المتصفحات تدعمه، لذا عليك التأكد من توافق بيئة تطبيقك معه.

3. التسلسل المخصّص: تبسيط المشكلات المعقّدة

مع أن الأساليب السابقة تكفي لحل معظم المشكلات، فقد نواجه أحيانًا حالات تتطلب تحكمًا أدق. عندئذ،Jackson يصبح المُسلسِل المخصّص الخاص به مفيدًا هنا.

يمكننا كتابة مُسلسِل مخصص للتحكم بمرونة، وفقًا للاحتياجات، في Long عملية تسلسل الحقول من هذا النوع. فمثلًا، يمكننا أثناء التسلسل تحديد ما إذا كنا سنحوّل Long تحويله إلى سلسلة نصية، أو معالجته بطرق مختلفة وفق شروط محددة:

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class CustomLongSerializer extends JsonSerializer<Long> {

    @Override
    public void serialize(Long value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        if (value != null) {
            gen.writeString(value.toString());
        }
    }
}


ثم طبّق هذا المُسلسِل المخصّص حيث يلزم:

import com.fasterxml.jackson.databind.annotation.JsonSerialize;

public class UserDto {
    @JsonSerialize(using = CustomLongSerializer.class)
    private Long id;
    // 其他字段
}


بهذا، يمكنك التحكّم في عملية التسلسل بأكملها، وضمان أن كل Long يمكن التعامل معها جميعًا بالطريقة التي تريدها. وبالطبع، رغم مرونة هذه الطريقة، فإنها أكثر تعقيدًا بعض الشيء، وتتطلب كتابة بضعة أسطر إضافية من الشيفرة، وقد تزيد تكلفة الصيانة أيضًا.

4. المعالجة الشاملة:حل يوفّر الوقت والجهد

إذا أردت معالجة هذه المشكلة على مستوى التطبيق كله وتجنّب عناء إعداد التعليقات التوضيحية لكل حقل، فيمكنك التفكير في إعداد عام ObjectMapper. من خلال القيام بذلك في Spring Boot ضبط الإعداد العام ضمن ObjectMapper، يمكنك جعل جميع Long تُحوَّل حقول هذا النوع كلها تلقائيًا إلى سلاسل نصية.

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        mapper.registerModule(module);
        return mapper;
    }
}


حل نهائي يغنيك عن القلق من نسيان إعداد مُسلسِل لأي حقل، ويتيح للمشروع كله الاستفادة من الحفاظ على الدقة دون فقدان. وبالطبع، عند استخدام الإعداد العام، ينبغي الانتباه إلى أنه قد يؤثر في بعض ما لا ترغب في تحويله من Long من الحقول، ولذلك يجب التفكير في الأمر بحذر.

حالة عملية: رحلة حل خلل برمجي مليئة بالتقلبات

لكي تفهموا بصورة أفضل الأثر العملي لهذه الحلول، سأشارككم قصة من مشروع حقيقي. كان هذا المشروع يتعلق بإدارة معرّفات المستخدمين، وبسبب العدد الكبير للمستخدمين، اعتمدت الواجهة الخلفية على Long من المعرّفات الفريدة لهذا النوع. وبعد أيام قليلة من إطلاق المشروع، بدأت ترد ملاحظات من المستخدمين: «لماذا تغيّر شكل معرّفي ID؟».

الخطوة الأولى: تتبع أصل المشكلة

فحصنا أولًا شيفرة الواجهة الأمامية، فوجدنا JavaScript الخاص بـ Number يعجز النوع فعلًا عن تمثيل هذا العدد المكون من 19 خانة بدقة، مما أدى إلى فقدان الدقة. ثم راجعنا شيفرة الواجهة الخلفية، فوجدنا أنه على الرغم من أن Long تكون بيانات هذا النوع صحيحة في الواجهة الخلفية، ولكن عند تمريرها عبر REST API فُقدت دقة العدد عند إعادته إلى الواجهة الأمامية.

الخطوة الثانية:اختيار الحل المناسب

لحل المشكلة بسرعة، قررنا أولًا اعتماد @JsonSerialize(using = ToStringSerializer.class) هذه الطريقة البسيطة والفعالة. وبفضلها، نجحنا في تجنب تلقّي الواجهة الأمامية أرقامًا فقدت دقتها.

لكن المشكلة لم تُحل بالكامل. ففي المتطلبات اللاحقة، احتاجت الواجهة الأمامية إلى إجراء بعض العمليات على المعرّفات ID، مثل ترتيب معرّفات المستخدمين. وهنا بدت السلاسل النصية عاجزة بعض الشيء عن تلبية المطلوب.

الخطوة الثالثة: الاستخدام BigInt حل مشكلات العمليات الحسابية في الواجهة الأمامية

لذلك، قررنا إدخال BigInt。من خلال استخدام BigInt، تستطيع الواجهة الأمامية تخزين هذه المعرّفات الطويلة جدًا بدقة، وكذلك إجراء العمليات الحسابية اللازمة عليها. وأظهرت الاختبارات أن هذه الطريقة تعمل جيدًا على مختلف المتصفحات الرئيسية، وعيبها الوحيد هو ضعف دعم بعض إصدارات المتصفحات القديمة.

الخطوة الرابعة: الإعداد العام النهائي

لتجنب تكرار مشكلات مماثلة مستقبلًا، قررنا أن ToStringSerializer تهيئته ليسري على المستوى العام. وهذا يجعل جميع Long تُحوَّل بيانات هذا النوع تلقائيًا إلى سلاسل نصية عند التسلسل، مما يضمن دقة البيانات في الواجهة الأمامية ويقلّل أيضًا تكرار إعدادات الشيفرة.

خاتمة:القضاء على فقدان الدقة

يمكن أن نرى من هذه الحالة أنه، على الرغم من Long قد تبدو مشكلة فقدان الدقة في هذا النوع بسيطة، لكنها قد تُخفي مخاطر عديدة في المشاريع الفعلية. ومن خلال مقارنة حلول متعددة وتجربتها، توصلنا في النهاية إلى الحل الأنسب لمشروعنا.

آمل أن تساعدك هذه التدوينة المرحة والمفصلة عند التعامل مع Long لتجنب الالتفافات غير الضرورية عند مواجهة مشكلة فقدان دقة النوع. إذا مررت بتجربة مشابهة، فلا تتردد في مشاركة قصتك في التعليقات؛ فقد تمنح تجربتك مطورين آخرين مزيدًا من الإلهام.

وأخيرًا، تذكّروا أن الشيفرة كالحياة، فلا داعي للخوف من «فقدان» عارض؛ المهم العثور على «المُسلسِل» المناسب لإعادتها إلى مسارها الصحيح. أتمنى ألّا «تفقد الدقة» شيفراتكم مجددًا، وأن تظل دقيقة وتسير بسلاسة طوال الطريق!


التعليقات

جارٍ الإرسال

اختر المظهر المفضل

يُطبّق المظهر على الموقع كله.