第10篇 异常处理——程序的容错机制

**作者:**中文编程倡导者—— 李金雨
联系方式: wbtm2718@qq.com
**目标读者:**编程入门(零基础)
核心理念: 使用华为仓颉原生中文编程,体验真正的国产编程语言


一、什么是异常?

想象一下这些场景:

  • 你打开一个文件,但文件不存在
  • 你输入"abc",但程序期待一个数字
  • 你访问数组的第100个元素,但数组只有10个元素
  • 你做除法时,除数是0

这些情况都会导致程序"出错",专业术语叫做抛出异常(Throw Exception)。如果不处理,程序就会崩溃退出。

异常处理就是让程序在遇到错误时,能够优雅地处理,而不是直接"死掉"。

二、生活中的异常处理

例子1:餐厅点餐

  • 正常情况:点了一份炒饭,厨师做好了端上来
  • 异常情况:厨师发现米饭用完了
  • 处理方式:服务员告诉你"抱歉,米饭用完了,要不要试试面条?"

例子2:ATM取钱

  • 正常情况:输入密码正确,取出现金
  • 异常情况:密码错误、余额不足、机器故障
  • 处理方式:显示友好的提示信息,而不是让机器死机

三、仓颉语言中的异常处理

仓颉使用 try-catch 机制来处理异常。

基础语法

import std.console.*
import std.convert.*

func 安全地除法(被除数: Int64, 除数: Int64): Int64 {
    try {
        return 被除数 / 除数
    } catch (异常: Exception) {
        println("发生错误:${异常.message}")
        return 0  // 出错时返回默认值
    }
}

main() {
    var 结果1 = 安全地除法(10, 2)   // 正常:5
    var 结果2 = 安全地除法(10, 0)   // 异常:除数为0
    
    println("结果1:${结果1}")
    println("结果2:${结果2}")
}

运行结果

发生错误:division by zero
结果1:5
结果2:0

四、常见的异常类型

import std.console.*

func 演示各种异常(): Unit {
    // 1. 数组越界异常
    try {
        var 数字列表 = [1, 2, 3]
        var 值 = 数字列表[10]  // 越界了!
    } catch (异常: IndexOutOfBoundsException) {
        println("数组越界:${异常.message}")
    }
    
    // 2. 数字转换异常
    try {
        var 文本 = "abc"
        var 数字 = Int64.parse(文本)  // 无法转换!
    } catch (异常: NumberFormatException) {
        println("数字格式错误:${异常.message}")
    }
    
    // 3. 空指针异常
    try {
        var 空名字: String? = null
        var 长度 = 空名字!.length     // 空值解引用!
    } catch (异常: NullPointerException) {
        println("空指针异常:${异常.message}")
    }
}

五、抛出异常

有时候我们需要自己抛出异常来提示调用者。

// 定义一个检查年龄的函数
func 设置年龄(年龄: Int64): Unit {
    if (年龄 < 0) {
        throw Exception("年龄不能为负数!")
    }
    if (年龄 > 150) {
        throw Exception("年龄不能超过150岁!")
    }
    println("年龄设置成功:${年龄}岁")
}

main() {
    try {
        设置年龄(25)    // 正常
        设置年龄(-5)    // 抛出异常
    } catch (异常: Exception) {
        println("错误:${异常.message}")
    }
}

运行结果

年龄设置成功:25岁
错误:年龄不能为负数!

六、语法设计讨论:异常处理中的类型声明

同学们,在学习异常处理时,我们再次遇到了仓颉的类型后置语法问题。

看看异常处理的代码:

try {
    // 可能出错的代码
} catch (异常: Exception) {     // 类型后置!
    // 处理异常
}

func 安全地除法(被除数: Int64, 除数: Int64): Int64 {  // 参数和返回值类型都后置
    try {
        return 被除数 / 除数
    } catch (异常: Exception) {  // 又是类型后置
        return 0
    }
}

按照中国人的语言习惯:

  • 我们习惯说"异常类型的变量"、“整数类型的参数”
  • 我们习惯说"字符串类型的返回值"

但仓颉的写法:

  • catch (异常: Exception) → 读作"捕获异常,异常类型的"(定语后置)
  • func 函数名(): 返回类型 → 读作"函数名,返回某某类型的"(定语后置)

如果仓颉能改进成C#风格:

// 假设的改进语法
try {
    // 可能出错的代码
} catch (Exception 异常) {     // "异常类型的异常变量"——定语前置!
    // 处理异常
}

Int64 安全地除法(Int64 被除数, Int64 除数) {  // "整数类型的安全地除法函数"
    try {
        return 被除数 / 除数
    } catch (Exception 异常) {
        return 0
    }
}

这样读起来多么自然:“定义一个整数类型的函数安全地除法,它有两个整数类型的参数…”

华为在设计仓颉语言时,采用了类似Rust的类型后置语法,这就像是在说"异常 异常类型的"而不是"异常类型的异常"。现代汉语都是定语前置的,我们希望中文编程语言能真正符合中国人的语言习惯!

七、finally块

finally 块中的代码无论是否发生异常都会执行,常用于清理资源。

func 读取文件(文件名: String): String {
    var 文件内容 = ""
    var 文件: File? = null
    
    try {
        文件 = File.open(文件名, "r")
        文件内容 = 文件.readAll()
        return 文件内容
    } catch (异常: Exception) {
        println("读取文件失败:${异常.message}")
        return ""
    } finally {
        // 无论成功与否,都要关闭文件
        if (文件 != null) {
            文件.close()
            println("文件已关闭")
        }
    }
}

八、自定义异常

我们可以创建自己的异常类型来表示特定的错误。

// 自定义异常类
class 年龄异常 : Exception {
    init(消息: String) {
        super(消息)
    }
}

class 成绩异常 : Exception {
    init(消息: String) {
        super(消息)
    }
}

// 使用自定义异常
func 设置学生成绩(成绩: Float64): Unit {
    if (成绩 < 0) {
        throw 成绩异常("成绩不能为负数!")
    }
    if (成绩 > 100) {
        throw 成绩异常("成绩不能超过100分!")
    }
    println("成绩设置成功:${成绩}分")
}

main() {
    try {
        设置学生成绩(85.5)
        设置学生成绩(105)   // 抛出成绩异常
    } catch (异常: 成绩异常) {
        println("成绩错误:${异常.message}")
    } catch (异常: Exception) {
        println("其他错误:${异常.message}")
    }
}

九、练习时间

练习1:安全的计算器

创建一个计算器类,包含加、减、乘、除四个方法。除法要处理除数为0的情况,所有方法都要处理可能的溢出异常。

练习2:用户注册验证

创建一个用户注册函数,验证以下内容:

  • 用户名不能为空,长度3-20个字符
  • 密码不能为空,长度6-30个字符
  • 年龄必须在1-150之间
  • 邮箱格式要正确

如果验证失败,抛出相应的异常。

练习3:银行转账系统

创建一个转账函数,需要处理以下异常情况:

  • 转出账户余额不足
  • 转账金额必须大于0
  • 转账金额不能超过单笔限额(比如5万元)
  • 收款账户不存在

十、本课小结

今天我们学习了:

  1. 异常:程序运行时的错误情况
  2. try-catch:捕获并处理异常
  3. throw:主动抛出异常
  4. finally:无论是否异常都会执行的代码块
  5. 自定义异常:创建特定的异常类型

类型语法思考:在异常处理中,我们频繁地声明异常变量类型、函数参数类型、返回值类型,每一次都要面对类型后置的语法。希望未来的中文编程语言能让类型声明更符合中国人的说话习惯,真正做到"说人话"!

十一、课后作业

  1. 完善"学生管理系统"的异常处理:

    • 添加成绩输入验证(0-100分)
    • 添加年龄输入验证(5-30岁)
    • 添加学号格式验证(不能为空,长度固定)
  2. 编写一个文件复制程序,要求:

    • 检查源文件是否存在
    • 检查目标目录是否有写入权限
    • 使用try-catch-finally确保资源正确释放
  3. 思考:在你的日常生活中,还有哪些场景可以用"异常处理"的思路来解决?


下篇预告:第11篇《文件操作——数据的持久化存储》,学习如何将数据保存到文件中,让数据不会随程序关闭而丢失!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐