免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Three Rules for Effective Exception Handling
Java的異常處理機(jī)制提供了一個統(tǒng)一的機(jī)制來識別和響應(yīng)程序錯誤.一個有效的異常處理方法可以使得你的程序健壯并易于調(diào)試.因為異??梢詫ξ覀兘獯鹣旅孢@些問題提供些幫助,因此它是一個對我們極有用的調(diào)試工具.
錯誤是什么?
什么地方發(fā)生錯誤?
為什么發(fā)生錯誤?
(即:3W what、where、why)
當(dāng)異常使用恰當(dāng)時,所拋出異常的類型可以表明什么程序錯誤發(fā)生了,而Stack Trace可以告訴我們什么地方發(fā)生了錯誤.至于為什么發(fā)生錯誤則可以通過看異常信息和Stack Trace來了解.如果你發(fā)現(xiàn)你的異常不能回答以上所有問題,那一定是你沒有把它們用對.當(dāng)調(diào)試程序時,有三個原則將會幫助你更好的使用異常即:具體化、早throw及晚Catch.。
為了說明異常處理的這三個原則,我們將討論一個叫作JCheckbook的虛擬個人財務(wù)管理系統(tǒng)。Jcheckbook可以用作記錄和追蹤銀行帳戶的活動,比如存款、取款及開支票。Jcheckbook最初的版本以桌面程序運行,但未來的計劃要求以HTML和C/S applet 做為客戶端的實現(xiàn)。
具體化
JAVA定義了異常類的層次結(jié)構(gòu),從Trowable開始, Error和Exception則繼承于它,而RuntimeException則繼承了Exception.如圖所示:
這四個類的抽象層次都很高,它們對了解到底程序發(fā)生了什么幫助極少,當(dāng)你有權(quán)實例化這些類的時候,最好將它們視為基類,而使用更特殊的子類,JAVA提供了相當(dāng)數(shù)量的異常子類,并且你也可以對一些特殊的情況定義自己的異常類。
比如說,JAVA的IO包定義了繼承于Exception的IOException類,更特殊一點就是FileNotFoundException,EOFException,,ObjectStreamException和所有的IOException的子類。每個異常類都描述了一種特定的與IO有關(guān)的錯誤,它們分別表示:文件丟失,文件意外結(jié)束,或者一個被破壞的序列化對象流。異常類越具體,對回答程序發(fā)生了什么錯誤就越有幫助。
在Catch異常時,捕捉具體的異常類顯得特別重要,比如,Jcheckbook遇到FileNotFoundException可能會要求你提供另外一個文件。如果是EOFException,程序可能繼續(xù)運行,只不過提示一下在異常拋出之前文件是可以被正確地讀出來的。如一個ObjectStreamException異常拋出了,程序則可能通知用戶所讀的文件已經(jīng)被破壞了,并且要求需要提供一個備份或其它的文件。
 
Translator comments:
這也就是為什么要將異常類分類,以此讓每個類特定地代表一種程序錯誤的原因?同時也是為什么在方法聲明中throws某個特定的異常類,而不是如:throw Exception?也是為什么要在Catch中捕獲具體的異常對象,而不是形如:try{}catch(Excetion e ){}的原因?
 
因為在JAVA中,一個try程序塊是可以指定多個Catch塊,因此就很容易去捕捉具體的異常,然后針對不同的異常提供相應(yīng)的處理方案。
File prefsFile = new File(prefsFilename);
try
{
    readPreferences(prefsFile);
}
catch (FileNotFoundException e)
{
    // alert the user that the specified file
    // does not exist
}
catch (EOFException e)
{
    // alert the user that the end of the file
    // was reached
}
catch (ObjectStreamException e)
{
    // alert the user that the file is corrupted
}
catch (IOException e)
{
    // alert the user that some other I/O
    // error occurred
}
Jcheckbook為了對所捕獲到的異常給用戶提供比較具體和特殊點的錯誤信息,應(yīng)用了多個Catch塊。比如,如果捕獲的是FileNotFoundException,它可能通知用戶重新指定一個文件。
額外的編寫多個Catch塊,在某些情況下,可能是多余的負(fù)擔(dān),但是在這個例子中,它確實可以使程序以一種更加友好的方式回應(yīng)程序發(fā)生的各種錯誤。
如果是IOException而不是我們起初所指定的那三個異常被拋出了,那么最后一個Catch塊將處理并提供一個一般意義上的錯誤信息。這樣,程序在提供某些具體的錯誤信息的同時也可以對意想不到的那些與文件有關(guān)的異常提供一般意義上的處理。
有時候,開發(fā)人員直接捕獲Exception,然后顯示異常類的名字和Stack Trace信息,但為了具體問題具體處理,請不要這樣做??吹狡聊簧系膉ava.io.EOFException或者Stack Trace信息可能會使用戶迷惑,而不是幫助他。捕獲具體的異常從而用英語或其它語言給用戶一個與問題相關(guān)提示,同時將異常的Stack Trace放到LOG文件中。異常和Stack Trace對開發(fā)人員意味著一個有力的調(diào)試工具,而對用戶卻毫無用處。
最后,請注意到Jcheckbook將捕獲和處理Exception類型異常推遲到用戶界面上,而不是放到readPreferences()函數(shù)里。這樣在界面上可以以對話框的形式提示的用戶相關(guān)信息或者使用其它的處理方式。這就是我們待會兒將會討論的“晚捕獲”原則。
 
Translator comments:
所謂具體化為:
     定義具體的代表某個特定錯誤的異常類。
     方法聲明特定的異常類。
     方法捕獲具體類。
目地:具體問題具體處理。
 
早拋出
     通過Stack trace向我們展示的引起異常的方法調(diào)用順序、類名、方法名、原代碼文件名以及每個方法調(diào)用的行號,這樣可以幫助我們精確地定位異常發(fā)生的地方??紤]下面的Stack trace信息:
java.lang.NullPointerException
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:103)
    at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)
    at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
    at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)
這表明類FileInputStream的open方法拋出一個NullPointerException異常,但注意到FileInputStream.open()是JAVA標(biāo)準(zhǔn)類庫的一部分。這樣引起異常的原因很可能在我們自己代碼里面,而不JAVA API,所以問題一定出在這之前的某個方法內(nèi),幸運的是它被顯示了出來。
很不幸,異常NullPointerException恰好是JAVA中能提供有效信息最少的異常類中的一個。它并不能告訴我們真正想知道的。同時我們也需要向后追蹤去找到錯誤發(fā)生地。
     通過向后追蹤Stack trace和檢查我們的代碼,我們發(fā)現(xiàn)錯誤是由于調(diào)用方法readPreferences()時傳入的文件名為null,因為方法知道一個空文件名將使方法不能再執(zhí)行下去,所以它立即檢查這個條件:
public void readPreferences(String filename)
    throws IllegalArgumentException
{
    if (filename == null)
    {
        throw new IllegalArgumentException
                            ("filename is null");
    } //if
   
    //...perform other operations...
   
    InputStream in = new FileInputStream(filename);
   
    //...read the preferences file...
}
因為比較早的拋出了異常,所以異常就變得更加具體和準(zhǔn)確了。Stack trace也很準(zhǔn)確地反映出發(fā)生了什么異常,為什么及在什么地方。這樣使得Stack trace更加準(zhǔn)確的反映了本來程序所發(fā)生的一切:
java.lang.IllegalArgumentException: filename is null
    at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207)
    at jcheckbook.JCheckbook.startup(JCheckbook.java:116)
    at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)
at jcheckbook.JCheckbook.main(JCheckbook.java:318)
另外,異常信息(“filename is null”)指出了是什么為空,從而使得異常附帶更多有用的信息。而這些是我們無法從異常NullPointerException中獲得的。
一旦出錯誤就立即拋出異常,這樣可以避免再去構(gòu)造或找開那些不再需要的對象或資源。比如說文件或網(wǎng)絡(luò)連接。與打開這些資源相關(guān)的清理工作也可以避免了。
 
晚捕獲
     許多JAVA開發(fā)人員,無論新手或老手都普遍地犯一個錯誤就是在程序有能力處理一個異常之前就將它捕獲了。JAVA編譯器堅持讓Checked Exception不是要被捕獲就一定要在函數(shù)頭中聲明的作法也客觀上促使了程序員采用上面這一錯誤作法。對程序員來說,一個很自然的趨勢就是讓代碼包括在try程序塊中并捕捉異常以阻止編譯器報錯。
問題是當(dāng)捕獲到異常之后你該如何處理它了?最壞的事情就是不做任何處理。一個空的catch塊使異常無端地消失了從而使得關(guān)于異常的What、Where、Why信息永遠(yuǎn)丟失了。將異常的信息Log下來使情況稍好點,畢竟那樣還有關(guān)于異常信息的記錄。但是我們不可能期望用戶想看甚至看懂Log文件和Stack trace信息。在函數(shù)readPreferences()內(nèi)顯示異常信息對話框是不合適的。因為Jcheckbook目前運行為桌面程序,我們也計劃將其改寫為基于HTML或者C/S的版本。
功能從Server上獲取,而錯誤需要在瀏覽器或客戶端中顯示。我們應(yīng)該帶著為未來著想的想法去設(shè)計readPreferences()方法。恰當(dāng)?shù)膶⒂脩艚换ゴa從程序邏輯中分開可以增加代碼的可重用性。
在有能力處理一個異常之前去捕獲它,常常會進(jìn)一步地引起其它的錯誤和異常。比如,像readPreferences()方法立即捕獲并處理了在調(diào)用FileInputStream構(gòu)造函數(shù)所產(chǎn)生的FileNotFoundException異常,代碼如下
public void readPreferences(String filename)
{
    //...
   
    InputStream in = null;
   
    // DO NOT DO THIS!!!
    try
    {
        in = new FileInputStream(filename);
    }
    catch (FileNotFoundException e)
    {
        logger.log(e);
    }
   
    in.read(...);
   
    //...
}
這段代碼在對恢復(fù)錯誤無能為力的情況下捕捉了異常FileNotFoundException。如果文件沒有找到,剩下的方法體肯定執(zhí)行不起來。用一個不存在的文件名去調(diào)用方法readPreferences()將會發(fā)生什么?當(dāng)然,異常FileNotFoundException將會被Log,如果我們恰好去檢查Log文件,我們將會意識到異常的發(fā)生。但如果程序繼續(xù)去讀那個文件當(dāng)中的數(shù)據(jù),那會發(fā)生什么了?因為文件不存在,in是null,所以一個NullPointerException將會被拋出。
當(dāng)是調(diào)試程序時,直覺會使我們在日志文件中去檢查最近的信息,你會非常恐懼的看到一個NullPointerException異常,因為它太一般了,不能提供你有價值的對判斷異常發(fā)生原因有幫助的任何信息,Stack trace也是,這不僅使你無法了解錯誤是什么(真的錯誤是FileNotFoundException而不是NullPointerException),而且也無法了解錯誤發(fā)生的正確地點。
 
除了catch異常外,readPreferences()方法還能對異常做何處理了?可能跟我們的直覺相反,僅僅將它上拋就可以了,不要立即catch異常。將處理的責(zé)任上拋給readPreferences()的調(diào)用者,讓它去決定采用合適的方法去處理文件引用丟失的問題,它可以提示用戶要求另一個文件、使用默認(rèn)的值或者如果沒有其它的辦法,提示用戶有問題發(fā)生并退出程序的運行也是可以。
這種將異常處理的任務(wù)上拋給它的調(diào)動鏈上的方法就是用throws關(guān)鍵字在方法頭聲明這些需要上拋的異常。當(dāng)聲明這些異常時,請盡可能使用能代表具體問題的異常類。這樣可以使調(diào)動你方法的程序可以預(yù)料到到底會發(fā)生哪些異常并根椐具體情況處理它們。將前面代碼修改如下:
public void readPreferences(String filename)
    throws IllegalArgumentException,
           FileNotFoundException, IOException
{
    if (filename == null)
    {
        throw new IllegalArgumentException
                        ("filename is null");
    } //if
   
    //...
   
    InputStream in = new FileInputStream(filename);
   
    //...
}
從技術(shù)上說,我們唯一需要聲明的就是IOException,但是通過聲明此方法可能會拋出FileNotFoundException可以幫助調(diào)用者更好的處理異常。而異常IllegalArgumentException是不需要聲明的,因為它是一個Unchecked Exception(為RuntimeException的子類)仍然將其包括在其中是因為這樣使代碼更清晰[特別對調(diào)用者而言]。
當(dāng)然,最終你的程序仍需捕獲異常,要么程序就會意外地中止。但這個原則的意義就在于,它可以讓你在合適地方捕獲異常并能夠提供合適的異常處理方法,不會再引起其它的異常,從而讓程序能繼續(xù)運行?;蛘咛峁┯脩粢恍┚唧w有用的信息,包括如何從錯誤中恢復(fù)過來一些指示。當(dāng)一個方法對異常不能做這兩者之一時,簡單地將異常上拋,這樣它就會在合適的地方被捕獲并處理。
總結(jié)
有經(jīng)驗的開發(fā)人員都知道調(diào)試程序最難的部分不是修改Bug,而是找出Bug的藏身之處。通過使用上面所講的三個原則,你就可以使異常幫助你追蹤和清除Bugs并且使得你的程序更加健壯和友好。
Leon·He
Broaden Gate(ShenZhen) Corp
回歸簡單,嚴(yán)戒浮躁.
 
附原文:
 

Exceptions in Java provide a consistent mechanism for identifying and responding to error conditions. Effective exception handling will make your programs more robust and easier to debug. Exceptions are a tremendous debugging aid because they help answer these three questions:

  • What went wrong?
  • Where did it go wrong?
  • Why did it go wrong?

When exceptions are used effectively, what is answered by the type of exception thrown, where is answered by the exception stack trace, and why is answered by the exception message. If you find your exceptions aren't answering all three questions, chances are they aren't being used effectively. Three rules will help you make the best use of exceptions when debugging your programs. These rules are: be specific, throw early, and catch late.

To illustrate these rules of effective exception handling, this article discusses a fictional personal finance manager called JCheckbook. JCheckbook can be used to record and track bank account activity, such as deposits, withdrawals, and checks written. The initial version of JCheckbook runs as a desktop application, but future plans call for an HTML client and a client/server applet implementation.

Be Specific

Java defines an exception class hierarchy, starting with Throwable, which is extended by Error and Exception, which is then extended by RuntimeException. These are illustrated in Figure 1.


Figure 1. Java exception hierarchy

These four classes are generic and they don't provide much information about what went wrong. While it is legal to instantiate any of these classes (e.g., new Throwable()), it is best to think of them as abstract base classes, and work with more specific subclasses. Java provides a substantial number of exception subclasses, and you may define your own exception classes for additional specificity.

For example, the java.io package defines the IOException subclass, which extends Exception. Even more specific are FileNotFoundException, EOFException, andObjectStreamException, all subclasses of IOException. Each one describes a particular type of I/O-related failure: a missing file, an unexpected end-of-file, or a corrupted serialized object stream, respectively. The more specific the exception, the better our program answers what went wrong.

It is important to be specific when catching exceptions, as well. For example, JCheckbook may respond to a FileNotFoundException by asking the user for a different file name. In the case of an EOFException, it may be able to continue with just the information it was able to read before the exception was thrown. If an ObjectStreamException is thrown, the program may need to inform the user that the file has been corrupted, and that a backup or a different file needs to be used.

Java makes it fairly easy to be specific when catching exceptions because we can specify multiple catch blocks for a single try block, each handling a different type of exception in an appropriate manner.

File prefsFile = new File(prefsFilename);try{readPreferences(prefsFile);}catch (FileNotFoundException e){// alert the user that the specified file// does not exist}catch (EOFException e){// alert the user that the end of the file// was reached}catch (ObjectStreamException e){// alert the user that the file is corrupted}catch (IOException e){// alert the user that some other I/O// error occurred}

JCheckbook uses multiple catch blocks in order to provide the user with specific information about the type of exception that was caught. For instance, if a FileNotFoundException was caught, it can instruct the user to specify a different file. The extra coding effort of multiple catch blocks may be an unnecessary burden in some cases, but in this example, it does help the program respond in a more user-friendly manner.

Should an IOException other than those specified by the first three catch blocks be thrown, the last catch block will handle it by presenting the user with a somewhat more generic error message. This way, the program can provide specific information when possible, but still handle the general case should an unanticipated file-related exception "slip by."

Sometimes, developers will catch a generic Exception and then display the exception class name or stack trace, in order to "be specific." Don't do this. Seeing java.io.EOFException or a stack trace printed to the screen is likely to confuse, rather than help, the user. Catch specific exceptions and provide the user with specific information in English (or some other human language). Do, however, include the exception stack trace in your log file. Exceptions and stack traces are meant as an aid to the developer, not to the end user.

Finally, notice that instead of catching the exception in the readPreferences() method, JCheckbook defers catching and handling the exception until it reaches the user interface level, where it can alert the user with a dialog box or in some other fashion. This is what is meant by "catch late," as will be discussed later in this article.

Throw Early

The exception stack trace helps pinpoint where an exception occurred by showing us the exact sequence of method calls that lead to the exception, along with the class name, method name, source code filename, and line number for each of these method calls. Consider the stack trace below:

java.lang.NullPointerExceptionat java.io.FileInputStream.open(Native Method)at java.io.FileInputStream.<init>(FileInputStream.java:103)at jcheckbook.JCheckbook.readPreferences(JCheckbook.java:225)at jcheckbook.JCheckbook.startup(JCheckbook.java:116)at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)at jcheckbook.JCheckbook.main(JCheckbook.java:318)

This shows that the open() method of the FileInputStream class threw a NullPointerException. But notice that FileInputStream.close() is part of the standard Java class library. It is much more likely that the problem that is causing the exception to be thrown is within our own code, rather than the Java API. So the problem must have occurred in one of the preceding methods, which fortunately are also displayed in the stack trace.

What is not so fortunate is that NullPointerException is one of the least informative (and most frequently encountered and frustrating) exceptions in Java. It doesn't tell us what we really want to know: exactly what is null. Also, we have to backtrack a few steps to find out where the error originated.

By stepping backwards through the stack trace and investigating our code, we determine that the error was caused by passing a null filename parameter to the readPreferences() method. Since readPreferences() knows it cannot proceed with a null filename, it checks for this condition immediately:

public void readPreferences(String filename)throws IllegalArgumentException{if (filename == null){throw new IllegalArgumentException("filename is null");}  //if//...perform other operations...InputStream in = new FileInputStream(filename);//...read the preferences file...}

By throwing an exception early (also known as "failing fast"), the exception becomes both more specific and more accurate. The stack trace immediately shows what went wrong (an illegal argument value was supplied), why this is an error (null is not allowed for filename), and where the error occurred (early in the readPreferences() method). This keeps our stack trace honest:

java.lang.IllegalArgumentException: filename is nullat jcheckbook.JCheckbook.readPreferences(JCheckbook.java:207)at jcheckbook.JCheckbook.startup(JCheckbook.java:116)at jcheckbook.JCheckbook.<init>(JCheckbook.java:27)at jcheckbook.JCheckbook.main(JCheckbook.java:318)

In addition, the inclusion of an exception message ("filename is null") makes the exception more informative by answering specifically what was null, an answer we don't get from theNullPointerException thrown by the earlier version of our code.

Failing fast by throwing exceptions as soon as an error is detected can eliminate the need to construct objects or open resources, such as files or network connections, that won't be needed. The clean-up effort associated with opening these resources is also eliminated.

Catch Late

A common mistake of many Java developers, both new and experienced, is to catch an exception before the program can handle it in an appropriate manner. The Java compiler reinforces this behavior by insisting that checked exceptions either be caught or declared. The natural tendency is to immediately wrap the code in a try block and catch the exception to stop the compile from reporting errors.

The question is, what to do with an exception after it is caught? The absolute worst thing to do is nothing. An empty catch block swallows the exception, and all information about what, where, and why something went wrong is lost forever. Logging the exception is slightly better, since there is at least a record of the exception. But we can hardly expect that the user will read or even understand the log file and stack trace. It is not appropriate for readPreferences() to display a dialog with an error message, because while JCheckbook currently runs as a desktop application, we also plan to make it an HTML-based web application. In that case, displaying an error dialog is not an option. Also, in both the HTML and the client/server versions, the preferences would be read on the server, but the error needs to be displayed in the web browser or on the client. The readPreferences() method should be designed with these future needs in mind. Proper separation of user interface code from program logic increases the reusability of our code.

Catching an exception too early, before it can properly be handled, often leads to further errors and exceptions. For example, had the readPreferences() method shown earlier immediately caught and logged the FileNotFoundException that could be thrown while calling the FileInputStream constructor, the code would look something like this:

public void readPreferences(String filename){//...InputStream in = null;// DO NOT DO THIS!!!try{in = new FileInputStream(filename);}catch (FileNotFoundException e){logger.log(e);}in.read(...);//...}

This code catches FileNotFoundException, when it really cannot do anything to recover from the error. If the file is not found, the rest of the method certainly cannot read from the file. What would happen should readPreferences() be called with the name of file that doesn't exist? Sure, the FileNotFoundException would be logged, and if we happened to be looking at the log file at the time, we'd be aware of this. But what happens when the program tries to read data from the file? Since the file doesn't exist, in is null, and a NullPointerException gets thrown.

When debugging a program, instinct tells us to look at the latest information in the log. That's going to be the NullPointerException, dreaded because it is so unspecific. The stack trace lies, not only about what went wrong (the real error is a FileNotFoundException, not a NullPointerException), but also about where the error originated. The problem occurred several lines of code away from where the NullPointerException was thrown, and it could have easily been several method calls and classes removed. We end up wasting time chasing red herrings that distract our attention from the true source of the error. It is not until we scroll back in the log file that we see what actually caused the program to malfunction.

What should readPreferences() do instead of catching the exceptions? It may seem counterintuitive, but often the best approach is to simply let it go; don't catch the exception immediately. Leave that responsibility up to the code that calls readPreferences(). Let that code determine the appropriate way to handle a missing preferences file, which could mean prompting the user for another file, using default values, or, if no other approach works, alerting the user of the problem and exiting the application.

The way to pass responsibility for handling exceptions further up the call chain is to declare the exception in the throws clause of the method. When declaring which exceptions may be thrown, remember to be as specific as possible. This serves to document what types of exceptions a program calling your method should anticipate and be ready to handle. For example, the "catch late" version of the readPreferences() method would look like this:

public void readPreferences(String filename)throws IllegalArgumentException,FileNotFoundException, IOException{if (filename == null){throw new IllegalArgumentException("filename is null");}  //if//...InputStream in = new FileInputStream(filename);//...}

Technically, the only exception we need to declare is IOException, but we document our code by declaring that the method may specifically throw a FileNotFoundException.IllegalArgumentException need not be declared, because it is an unchecked exception (a subclass of RuntimeException). Still, including it serves to document our code (the exceptions should also be noted in the JavaDocs for the method).

Of course, eventually, your program needs to catch exceptions, or it may terminate unexpectedly. But the trick is to catch exceptions at the proper layer, where your program can either meaningfully recover from the exception and continue without causing further errors, or provide the user with specific information, including instructions on how to recover from the error. When it is not practical for a method to do either of these, simply let the exception go so it can be caught later on and handled at the appropriate level.

Conclusion

Experienced developers know that the hardest part of debugging usually is not fixing the bug, but finding where in the volumes of code the bug hides. By following the three rules in this article, you can help exceptions help you track down and eradicate bugs and make your programs more robust and user-friendly.

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
java中的讀文件
文件的輸入輸出
Android 保存圖片到本地相冊
FileReader讀取中文txt文件編碼丟失問題(亂碼)
Java文件常用操作集錦
關(guān)于java異常處理的幾個關(guān)鍵字 try catch/throw/throws
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服