أفضل الممارسات باستخدام خدمات واجهة برمجة التطبيقات للترميز الجغرافي

خدمات الويب في Google Maps Platform هي مجموعة من واجهات HTTP لخدمات Google، وهي توفر بيانات جغرافية لتطبيقات الخرائط.

يصف هذا الدليل بعض الممارسات الشائعة المفيدة في إعداد طلبات خدمة الويب ومعالجة ردود الخدمة. راجِع دليل المطوّر للاطّلاع على المستندات الكاملة الخاصة بواجهة برمجة التطبيقات Geocoding API.

ما المقصود بخدمة الويب؟

خدمات الويب في Google Maps Platform هي واجهة لطلب بيانات واجهة برمجة تطبيقات الخرائط من خدمات خارجية واستخدام البيانات ضمن تطبيقات الخرائط. تم تصميم هذه الخدمات للاستخدام مع خريطة، وفقًا لقيود الترخيص المُضمنة في بنود خدمة "منصة خرائط Google".

تستخدم خدمات الويب لـ Maps API طلبات HTTP(S) إلى عناوين URL محددة، مع تمرير معلمات عناوين URL و/أو بيانات POST بتنسيق JSON كوسيطات إلى الخدمات. وبشكل عام، تعرض هذه الخدمات بيانات في نص الاستجابة إما بتنسيق JSON أو XML لتحليل و/أو معالجة تطبيقك.

يكون الطلب النموذجي لواجهة برمجة التطبيقات Geocoding API بشكل عام على النحو التالي:

https://maps.googleapis.com/maps/api/geocode/output?parameters

حيث تشير السمة output إلى تنسيق الرد (عادةً json أو xml).

ملاحظة: تتطلب جميع تطبيقات واجهة برمجة التطبيقات Geocoding API المصادقة. مزيد من المعلومات عن بيانات اعتماد المصادقة

الوصول إلى طبقة المقابس الآمنة (SSL)/بروتوكول أمان طبقة النقل (TLS)

يجب استخدام HTTPS لجميع طلبات "منصة خرائط Google" التي تستخدم مفاتيح واجهة برمجة التطبيقات أو تحتوي على بيانات المستخدمين. قد يتم رفض الطلبات المقدَّمة عبر HTTP والتي تحتوي على بيانات حسّاسة.

إنشاء عنوان URL صالح

قد تعتقد أنّ عنوان URL "الصالح" هو عنوان واضح تمامًا، ولكن هذا ليس صحيحًا. على سبيل المثال، قد يحتوي عنوان URL الذي يتم إدخاله في شريط العناوين في أحد المتصفحات على رموز خاصة (مثل "上海+中國")، ويحتاج المتصفّح إلى ترجمة تلك الأحرف داخليًا إلى ترميز مختلف قبل الإرسال. وباستخدام الرمز المميّز نفسه، قد يتعامل أي رمز ينشئ إدخال UTF-8 أو يقبله مع عناوين URL التي تحتوي على أحرف UTF-8 على أنها "صالحة"، ولكنّه قد يحتاج أيضًا إلى ترجمة هذه الأحرف قبل إرسالها إلى خادم ويب. وتُعرف هذه العملية باسم ترميز عنوان URL أو ترميز النسبة المئوية.

الأحرف الخاصة

نحتاج إلى ترجمة الرموز الخاصة لأنّ جميع عناوين URL يجب أن تتوافق مع البنية المحدّدة في مواصفات معرِّف الموارد المنتظم (URI). ويعني ذلك في الواقع أنّ عناوين URL يجب أن تحتوي فقط على مجموعة فرعية خاصة من أحرف ASCII: الرموز الأبجدية الرقمية المألوفة وبعض الأحرف المحجوزة لاستخدامها كأحرف تحكّم في عناوين URL. يلخص هذا الجدول هذه الأحرف:

ملخّص أحرف عنوان URL الصالحة
تحديدالأحرفاستخدام عنوان URL
أحرف أبجدية رقمية سلاسل نصية واستخدام المخطط (http) والمنفذ (8080) وما إلى ذلك
غير محجوز - _ . ~ سلاسل نصية
تم الحجز ! * ' ( ) ; : @ & = + $ , / ? % # [ ] أحرف التحكّم و/أو السلاسل النصية

عند إنشاء عنوان URL صالح، يجب التأكّد من أنّه لا يحتوي إلا على تلك الأحرف المعروضة في الجدول. ويؤدي إنشاء عنوان URL لاستخدام هذه المجموعة من الأحرف بشكل عام إلى مشكلتين، إحداهما إغفال والاستبدال:

  • الأحرف التي تريد التعامل معها موجودة خارج المجموعة المذكورة أعلاه. على سبيل المثال، يجب ترميز الأحرف في اللغات الأجنبية، مثل 上海+中國، باستخدام الأحرف المذكورة أعلاه. وفقًا للاصطلاح الرائج، غالبًا ما يتم تمثيل المسافات (غير المسموح بها ضمن عناوين URL) باستخدام علامة الجمع '+' أيضًا.
  • توجد الأحرف ضمن المجموعة أعلاه كأحرف محجوزة، ولكن يجب استخدامها حرفيًا. على سبيل المثال، يتم استخدام ? ضمن عناوين URL للإشارة إلى بداية سلسلة طلب البحث. وإذا كنت تريد استخدام السلسلة "? والغموض"، عليك ترميز الحرف '?'.

يتم ترميز جميع الأحرف المطلوب ترميزها باستخدام عنوان URL باستخدام الحرف '%' وقيمة سداسية عشرية من حرفَين تتوافق مع حرف UTF-8 الخاص بها. على سبيل المثال، سيتم ترميز 上海+中國 في UTF-8 باستخدام عنوان URL كـ %E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B. وسيتم ترميز السلسلة ? and the Mysterians بعنوان URL كـ %3F+and+the+Mysterians أو %3F%20and%20the%20Mysterians.

الأحرف الشائعة التي تحتاج إلى ترميز

بعض الأحرف الشائعة التي يجب ترميزها هي:

حرف غير آمن قيمة مشفّرة
المساحة %20
" %22
< %3C
> %3E
# %23
% %25
| %7C

يكون تحويل عنوان URL الذي تتلقاه من إدخالات المستخدم أمرًا صعبًا في بعض الأحيان. على سبيل المثال، يمكن للمستخدم إدخال عنوان كـ "الشارع الخامس والرئيسي". وبشكل عام، يجب إنشاء عنوان URL من أجزائه مع التعامل مع أي إدخال يجريه المستخدم كأحرف حرفية.

بالإضافة إلى ذلك، تقتصر عناوين URL على 16384 حرفًا لجميع خدمات الويب على "منصة خرائط Google" وواجهات برمجة التطبيقات الثابتة على الويب. في معظم الخدمات، نادرًا ما يتم تجاوز هذا العدد من الأحرف. مع ذلك، يُرجى ملاحظة أنّ بعض الخدمات تتضمن عدة معلَمات قد تؤدي إلى إنشاء عناوين URL طويلة.

استخدام مهذب لـ Google APIs

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

إدارة الأخطاء وإعادة المحاولة

للحصول على معلومات عن رمزَي الاستجابة UNKNOWN_ERROR أو OVER_QUERY_LIMIT من واجهة برمجة التطبيقات Geocoding API، يمكنك الاطّلاع على إدارة الأخطاء ومحاولات إعادة المحاولة.

تراجع أسي

في حالات نادرة، قد يحدث خطأ أثناء تنفيذ طلبك؛ قد تتلقى رمز استجابة HTTP بمعيار 4XX أو 5XX أو قد يفشل اتصال TCP في مكان ما بين العميل وخادم Google. وغالبًا ما يكون من المفيد إعادة محاولة الطلب لأنه قد ينجح طلب المتابعة عند إخفاق الطلب الأصلي. ومع ذلك، من المهم عدم مجرد تكرار تقديم الطلبات إلى خوادم Google بشكل متكرر. ويمكن أن يؤدي سلوك التكرار هذا إلى زيادة الحمل على الشبكة بين العميل وGoogle مما يتسبب في مشاكل للعديد من الأطراف.

الطريقة الأفضل هي إعادة المحاولة مع زيادة التأخيرات بين المحاولات. وعادة ما يزيد التأخير من خلال عامل ضرب مع كل محاولة، وهو أسلوب يُعرف باسم الرقود الأسي.

على سبيل المثال، جرّب تطبيقًا يريد إرسال هذا الطلب إلى Time Zone API:

https://maps.googleapis.com/maps/api/timezone/json?location=39.6034810,-119.6822510&timestamp=1331161200&key=YOUR_API_KEY

يوضح مثال بايثون التالي كيفية تقديم الطلب باستخدام خوارزمية الرقود الأسي الثنائي:

import json
import time
import urllib.error
import urllib.parse
import urllib.request

# The maps_key defined below isn't a valid Google Maps API key.
# You need to get your own API key.
# See https://developers.google.com/maps/documentation/timezone/get-api-key
API_KEY = "YOUR_KEY_HERE"
TIMEZONE_BASE_URL = "https://maps.googleapis.com/maps/api/timezone/json"


def timezone(lat, lng, timestamp):

    # Join the parts of the URL together into one string.
    params = urllib.parse.urlencode(
        {"location": f"{lat},{lng}", "timestamp": timestamp, "key": API_KEY,}
    )
    url = f"{TIMEZONE_BASE_URL}?{params}"

    current_delay = 0.1  # Set the initial retry delay to 100ms.
    max_delay = 5  # Set the maximum retry delay to 5 seconds.

    while True:
        try:
            # Get the API response.
            response = urllib.request.urlopen(url)
        except urllib.error.URLError:
            pass  # Fall through to the retry loop.
        else:
            # If we didn't get an IOError then parse the result.
            result = json.load(response)

            if result["status"] == "OK":
                return result["timeZoneId"]
            elif result["status"] != "UNKNOWN_ERROR":
                # Many API errors cannot be fixed by a retry, e.g. INVALID_REQUEST or
                # ZERO_RESULTS. There is no point retrying these requests.
                raise Exception(result["error_message"])

        if current_delay > max_delay:
            raise Exception("Too many retry attempts.")

        print("Waiting", current_delay, "seconds before retrying.")

        time.sleep(current_delay)
        current_delay *= 2  # Increase the delay each time we retry.


if __name__ == "__main__":
    tz = timezone(39.6034810, -119.6822510, 1331161200)
    print(f"Timezone: {tz}")

كما يجب أن تكون حريصًا على ألا تكون هناك إعادة محاولة لرمز أعلى في سلسلة طلبات استدعاء التطبيقات يؤدي إلى تكرار الطلبات في تتابع سريع.

الطلبات المتزامنة

قد تبدو الأعداد الكبيرة من الطلبات المتزامنة إلى واجهات برمجة تطبيقات Google مثل هجوم الحرمان من الخدمات الموزعة (DDoS) على بنية Google الأساسية، وسيتم التعامل معها وفقًا لذلك. ولتجنب ذلك، عليك التأكد من عدم مزامنة طلبات واجهة برمجة التطبيقات بين البرامج.

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

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

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

بصرف النظر عن بداية الدقيقة، تكون أوقات المزامنة الشائعة الأخرى التي يجب عدم استهدافها هي بداية ساعة، وبدء كل يوم في منتصف الليل.

معالجة الردود

يتناول هذا القسم كيفية استخراج هذه القيم ديناميكيًا من استجابات خدمات الويب.

تقدم خدمات الويب في خرائط Google ردودًا يسهل فهمها، ولكنها ليست سهلة الاستخدام بالضبط. عند إجراء استعلام، بدلاً من عرض مجموعة من البيانات، ربما تريد استخراج بعض القيم المحددة. بشكل عام، ستحتاج إلى تحليل الردود من خدمة الويب واستخراج القيم التي تهمك فقط.

ويعتمد مخطط التحليل الذي تستخدمه على ما إذا كنت تعرض الناتج بتنسيق XML أو JSON. ويمكن معالجة استجابات JSON التي تكون في شكل كائنات JavaScript ضمن JavaScript نفسها على العميل. يجب معالجة استجابات XML باستخدام معالج XML ولغة طلب XML لمعالجة العناصر ضمن تنسيق XML. ونستخدم XPath في الأمثلة التالية، حيث إنه متوافق عادةً في مكتبات معالجة XML.

معالجة XML باستخدام XPath

XML هو تنسيق ناضج نسبيًا للمعلومات المنظمة لتبادل البيانات. وعلى الرغم من أنه ليس بسيطًا مثل JSON، إلا أنه يوفر دعمًا أكبر للّغات وأدوات أكثر فعالية. وعلى سبيل المثال، يكون الرمز المخصّص لمعالجة XML في Java مُدمَجًا في حزم javax.xml.

عند معالجة استجابات XML، يجب استخدام لغة استعلام مناسبة لتحديد العُقد داخل مستند XML بدلاً من افتراض وجود العناصر في مواضع مطلقة ضمن ترميز XML. XPath عبارة عن بنية لغة لوصف العُقد والعناصر بشكل فريد داخل مستند XML. تتيح لك تعبيرات XPath تحديد محتوى معين داخل مستند استجابة XML.

تعبيرات XPath

بعض الإلمام بمنصة XPath يقطع شوطًا طويلاً نحو تطوير مخطط تحليل قوي. سيركز هذا القسم على كيفية معالجة العناصر داخل مستند XML باستخدام XPath، ما يتيح لك معالجة عناصر متعددة وإنشاء استعلامات معقدة.

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

سنستخدم ملف XML المجرد التالي لتوضيح أمثلةنا:

<WebServiceResponse>
 <status>OK</status>
 <result>
  <type>sample</type>
  <name>Sample XML</name>
  <location>
   <lat>37.4217550</lat>
   <lng>-122.0846330</lng>
  </location>
 </result>
 <result>
  <message>The secret message</message>
 </result>
</WebServiceResponse>

تحديد العقدة في التعبيرات

تختار اختيارات XPath العُقد. تضم العقدة الجذر المستند بأكمله. يمكنك اختيار هذه العقدة باستخدام التعبير الخاص "/". يُرجى العلم أنّ العقدة الجذر ليست العقدة ذات المستوى الأعلى في مستند XML، بل إنها تقع على مستوى واحد فوق عنصر المستوى الأعلى هذا وتتضمّنها.

تمثل عُقد العناصر العناصر المتنوعة داخل شجرة مستندات XML. على سبيل المثال، يمثل العنصر <WebServiceResponse> عنصر المستوى الأعلى الذي يتم عرضه في نموذج الخدمة أعلاه. يمكنك اختيار العُقد الفردية إما من خلال مسار مطلق أو مسار نسبي، ويُشار إليه بوجود حرف "/" بادئ أو عدم توفّره.

  • المسار المطلق: يختار التعبير "/WebServiceResponse/result" جميع عُقد <result> التي هي عناصر ثانوية للعقدة <WebServiceResponse>. (يُرجى العلم أنّ هذين العنصرين ينحدان من العقدة الجذر "/").
  • المسار النسبي من السياق الحالي: سيتطابق التعبير "result" مع أي عناصر <result> ضمن السياق الحالي. وبشكل عام، لا داعي للقلق بشأن السياق، إذ تتم عادةً معالجة نتائج خدمة الويب من خلال تعبير واحد.

يمكن زيادة أيّ من هذين التعبيرَين من خلال إضافة مسار حرف بدل، ويُشار إليه بشرطة مائلة مزدوجة ("//"). يشير حرف البدل هذا إلى أنّه قد لا يتطابق أي عنصر أو أكثر في المسار المتداخل. على سبيل المثال، سيطابق تعبير XPath "//formatted_address" جميع عُقد هذا الاسم في المستند الحالي. سيطابق التعبير //viewport//lat جميع عناصر <lat> التي يمكن تتبّع <viewport> كعنصر رئيسي.

بشكل افتراضي، تتطابق تعبيرات XPath مع جميع العناصر. ويمكنك تقييد التعبير ليتطابق مع عنصر معين من خلال توفير predicate، المحاط بين قوسين مربعين ([]). على سبيل المثال، يعرض تعبير XPath "/GeocodeResponse/result[2] دائمًا النتيجة الثانية.

نوع التعبير
Root node
تعبير XPath:  "/"
الاختيار:
    <WebServiceResponse>
     <status>OK</status>
     <result>
      <type>sample</type>
      <name>Sample XML</name>
      <location>
       <lat>37.4217550</lat>
       <lng>-122.0846330</lng>
      </location>
     </result>
     <result>
      <message>The secret message</message>
     </result>
    </WebServiceResponse>
    
المسار المطلق
تعبير XPath:  "/WebServiceResponse/result"
الاختيار:
    <result>
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    </result>
    <result>
     <message>The secret message</message>
    </result>
    
مسار يتضمّن حرف بدل
تعبير XPath:  "/WebServiceResponse//location"
الاختيار:
    <location>
     <lat>37.4217550</lat>
     <lng>-122.0846330</lng>
    </location>
    
مسار مع عدّاد
تعبير XPath:  "/WebServiceResponse/result[2]/message"
الاختيار:
    <message>The secret message</message>
    
جميع العناصر الثانوية المباشرة لأول result
تعبير XPath:  "/WebServiceResponse/result[1]/*"
الاختيار:
     <type>sample</type>
     <name>Sample XML</name>
     <location>
      <lat>37.4217550</lat>
      <lng>-122.0846330</lng>
     </location>
    
تمثّل هذه السمة name للسمة result التي يكون نصها type عبارة عن "عيّنة".
تعبير XPath:  "/WebServiceResponse/result[type/text()='sample']/name"
الاختيار:
    Sample XML
    

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

تجدر الإشارة إلى أنّ XPath يتيح عُقد السمات أيضًا، إلا أنّ جميع خدمات الويب في "خرائط Google" تعرض عناصر بدون سمات، وبالتالي ليس من الضروري مطابقة السمات.

تحديد النص في التعبيرات

يتم تحديد نص داخل مستند XML في تعبيرات XPath من خلال عامل تشغيل عقدة نصية. يشير عامل التشغيل "text()" هذا إلى استخراج النص من العقدة المُشار إليها. على سبيل المثال، سيعرض تعبير XPath "//formatted_address/text()" كل النص داخل عناصر <formatted_address>.

نوع التعبير
جميع العُقد النصية (بما في ذلك المسافة البيضاء)
تعبير XPath:  "//text()"
الاختيار:
    sample
    Sample XML

    37.4217550
    -122.0846330
    The secret message
    
تحديد النص
تعبير XPath:  "/WebServiceRequest/result[2]/message/text()"
الاختيار:
    The secret message
    
اختيار حساس للسياق
تعبير XPath:  "/WebServiceRequest/result[type/text() = 'sample']/name/text()"
الاختيار:
    Sample XML
    

ويمكنك بدلاً من ذلك تقييم تعبير وعرض مجموعة من العُقد ثم تكرار "مجموعة العُقد" هذه لاستخراج النص من كل عُقدة. نستخدم هذا المنهج في المثال التالي.

لمزيد من المعلومات حول XPath، يمكنك الاطّلاع على مواصفات XPath W3C.

تقييم XPath في Java

تتيح Java بشكل كبير تحليل XML واستخدام تعبيرات XPath ضمن حزمة javax.xml.xpath.*. لهذا السبب، يستخدم الرمز النموذجي في هذا القسم Java لتوضيح كيفية التعامل مع XML وتحليل البيانات من استجابات خدمة XML.

لاستخدام XPath في رمز Java، يجب أولاً إنشاء مثيل لحدث XPathFactory واستدعاء newXPath() في هذا المصنع لإنشاء كائن XPath . ويمكن لهذا الكائن بعد ذلك معالجة تعبيرات XML وXPath التي تم تمريرها باستخدام طريقة evaluate().

عند تقييم تعبيرات XPath، احرص على تكرار أي "مجموعات عُقد" محتملة قد يتم عرضها. وبما أنّ هذه النتائج يتم عرضها كعُقد DOM في رمز Java، عليك الحصول على هذه القيم المتعدّدة في كائن NodeList وتكرار ذلك الكائن لاستخراج أي نص أو قيم من تلك العُقد.

يوضّح الرمز التالي كيفية إنشاء كائن XPath وتخصيصه باستخدام XML وتعبير XPath وتقييم التعبير لطباعة المحتوى ذي الصلة.

import org.xml.sax.InputSource;
import org.w3c.dom.*;
import javax.xml.xpath.*;
import java.io.*;

public class SimpleParser {

  public static void main(String[] args) throws IOException {

	XPathFactory factory = XPathFactory.newInstance();

    XPath xpath = factory.newXPath();

    try {
      System.out.print("Web Service Parser 1.0\n");

      // In practice, you'd retrieve your XML via an HTTP request.
      // Here we simply access an existing file.
      File xmlFile = new File("XML_FILE");

      // The xpath evaluator requires the XML be in the format of an InputSource
	  InputSource inputXml = new InputSource(new FileInputStream(xmlFile));

      // Because the evaluator may return multiple entries, we specify that the expression
      // return a NODESET and place the result in a NodeList.
      NodeList nodes = (NodeList) xpath.evaluate("XPATH_EXPRESSION", inputXml, XPathConstants.NODESET);

      // We can then iterate over the NodeList and extract the content via getTextContent().
      // NOTE: this will only return text for element nodes at the returned context.
      for (int i = 0, n = nodes.getLength(); i < n; i++) {
        String nodeString = nodes.item(i).getTextContent();
        System.out.print(nodeString);
        System.out.print("\n");
      }
    } catch (XPathExpressionException ex) {
	  System.out.print("XPath Error");
    } catch (FileNotFoundException ex) {
      System.out.print("File Error");
    }
  }
}