Go项目实战-让自定义Error支持Go的errors.Is判定以及原型模式的应用

经过前面三节高代码强度的学习,相信大家都已经有点累了,本节我们不着急继续“赶路”,休息片刻!我们换个轻松点的话题,聊一聊咱们项目定制化Error--AppError 怎么支持Go语言的 errors.Is 判定,以及项目预定义的那些Error在实际使用过程中某些情况下会出现循环引用的问题,我们会利用一个原型设计模式来解决这个问题。

a2570140a754181d7836ab3ecb49fd13.png

项目定制化Error 回顾

定义项目 Error 实现错误链和发生位置记录这篇文章中我们给项目定义了自己的Error类型 AppError

type AppError struct {
 code  int    `json:"code"`
 msg   string `json:"msg"`
 cause error  `json:"cause"`
 occurred string `json:"occurred"`
}

目的是为了通过自己的封装让Go的Error能支持错误原因和发生位置的记录。同时我们还为项目的开发预定义了很多Error变量。

// 用户模块相关错误码 10000100 ~ 1000199
var (
 ErrUserInvalid      = newError(10000101, "用户异常")
 ErrUserNameOccupied = newError(10000102, "用户名已被占用")
 ErrUserNotRight     = newError(10000103, "用户名或密码不正确")
)
// 商品模块相关错误码 10000200 ~ 1000299
var (
 ErrCommodityNotExists = newError(10000200, "商品不存在")
 ErrCommodityStockOut  = newError(10000201, "库存不足")
)
// 购物车模块相关错误码 10000300 ~ 1000399
var (
 ErrCartItemParam = newError(10000300, "购物项参数异常")
 ErrCartWrongUser = newError(10000301, "用户购物信息不匹配")
)

在 Go项目Error的统一管理和处理建议 中我建议需要返回给客户端返回约定好的错误码的错误都在这里预先定义。而对于一些像DB、存储之类错误产生的Error 无需告知客户端明确原因只需要让客户端发生了服务端内部错误的情况、或者不知道怎么处理的底层错误,我们先统一用Wrap方法把它包装成应用的AppError。

err = DBMaster().WithContext(ud.ctx).Create(userModel).Error
if err != nil {
    err = errcode.Wrap("UserDaoCreateUserError", err)
    return nil, err
}

在设计的过程中,觉得已经够全面了,但是只要真正把它来开发需求时,还是能发现有问题的,具体什么问题呢? 我们继续往下看。

怎么让自定义Error支持Go的errors.Is判定

想让Error支持Go的errors.Is 判定,我们先来看看errors.Is 方法里到底是怎么判定Error是不是给定的目标错误的,其源码如下:

func Is(err, target error) bool {
 if target == nil {
  return err == target
 }

 isComparable := reflectlite.TypeOf(target).Comparable()
 for {
  if isComparable && err == target {
   return true
  }
  if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
   return true
  }
  if err = Unwrap(err); err == nil {
   return false
  }
 }
}

其源码中逻辑是会一层层地解包error,每层的error都去看看是否已经实现了 interface{ Is(error) bool } 这个接口,如果实现了调用该层error的Is方法进行判定,如果跟给定error不相等或者没有实现Is接口,则继续用Uwrap解包,解到不能解为止 (err == nil)。

所以这里给我们预留了两个接口,我们让AppError实现 Is 和 Uwrap 方法后,它也就支持Go的 errors.Is 判定啦。

下面我们在 common/errcode/error.go 中加入这两个方法的实现。

package errcode

func (e *AppError) UnWrap() error {
 return e.cause
}

// Is 与上面的UnWrap一起让 *AppError 支持 errors.Is(err, target)
func (e *AppError) Is(target error) bool {
 targetErr, ok := target.(*AppError)
 if !ok {
  return false
 }
 return targetErr.Code() == e.Code()
}

关于项目自定义Error的优化,在课程中我还使用了这里使用设计模式里的原型模式, 把项目预定义的全局错误都是当作原型-prototype,保证我们既能规范管理我们项目的错误码,也能更自由放心地在程序中使用它们。

相关内容和详尽的实现代码,都涵盖在专栏中,现在扫码订阅专栏,一起加入学习吧

24c0dc5c9d61b0eafa6d0d9434db3ef2.png

本专栏分为五大部分,大部分内容已经更新完成

a92bd5d99d3a69e27af4f3db8c3bd15b.png
  • 第一部分介绍让框架变得好用的诸多实战技巧,比如通过自定义日志门面让项目日志更简单易用、支持自动记录请求的追踪信息和程序位置信息、通过自定义Error在实现Go error接口的同时支持给给错误添加错误链,方便追溯错误源头。

  • 第二部分:讲解项目分层架构的设计和划分业务模块的方法和标准,让你以后无论遇到什么项目都能按这套标准自己划分出模块和逻辑分层。后面几个部分均是该部分所讲内容的实践。

  • 第三部分:设计实现一个套支持多平台登录,Token泄露检测、同平台多设备登录互踢功能的用户认证体系,这套用户认证体系既可以在你未来开发产品时直接应用

  • 第四部分:商城app C端接口功能的实现,强化分层架构实现的讲解,这里还会讲解用责任链、策略和模版等设计模式去解决订单结算促销、支付方式支付场景等多种多样的实际问题。

  • 第五部分:单元测试、项目Docker镜像、K8s部署和服务保障相关的一些基础内容和注意事项

扫描上方二维码或者访问 https://xiaobot.net/p/golang 即刻订阅

/* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : main.c * @brief : Main program body ****************************************************************************** * @attention * * Copyright (c) 2025 STMicroelectronics. * All rights reserved. * * This software is licensed under terms that can be found in the LICENSE file * in the root directory of this software component. * If no LICENSE file comes with this software, it is provided AS-IS. * ****************************************************************************** */ /* USER CODE END Header */ /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "can.h" #include "tim.h" #include "gpio.h" #include "stm32f1xx_hal_iwdg.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ typedef enum { RECOVERY_IDLE, // 空闲状态,无恢复进行 RECOVERY_STOPPING,// 正在停止CAN RECOVERY_DELAYING,// 停止后的延时 RECOVERY_INITIALIZING,//重新初始化CAN外设 RECOVERY_CONFIGURING,//重新配置CAN过滤器 RECOVERY_STARTING, //重新启动CAN RECOVERY_ACTIVATING_NOTIFICATIONS,//重新激活CAN中断 RECOVERY_COMPLETE //恢复完成,清理状态 } RecoveryState_t; RecoveryState_t recoveryState = RECOVERY_IDLE;//恢复状态机当前状态 uint32_t recoveryStartTime = 0; //用于恢复过程中的延时计时 volatile uint8_t busOffFlag = 0; //总线关闭标志,在错误回调中置位 #define NUM_TX_MSGS 2 // 发送报文的数量 typedef struct { uint32_t id; // CAN ID uint8_t data[8]; // 数据 uint8_t dlc; // 数据长度 uint32_t interval; // 发送间隔(ms) uint32_t lastSendTime; // 上次发送的时间戳 } CanTxMsgConfig_t; CanTxMsgConfig_t txMsgConfigs[NUM_TX_MSGS] = { {0x123, {0}, 8, 20, 0}, // 0x123, 8字节数据, 20ms间隔 {0x124, {0}, 8, 20, 0} // ID 0x124, 8????, 20ms?? }; uint32_t TxMailbox; // 发送邮箱号,由HAL_CAN_AddTxMessage函数填充 //发送相关状态和统计变量 volatile uint32_t txBusyCount = 0; // 邮箱繁忙计数 volatile uint8_t consecutiveErrors = 0; //// 连续发送错误次数 #define MAX_CONSECUTIVE_ERRORS 10 // 最大连续错误次数,触发恢复 volatile uint8_t canSendFlag = 0; //定时器中断置位,主循环检查此标志决定是否处理发送 volatile uint8_t txInProgress = 0; // 发送正在进行标志,用于超时判断 volatile uint32_t txSuccessCount = 0; // 发送成功计数器 volatile uint32_t txErrorCount = 0; //发送失败计数器 volatile uint32_t rxErrorCount = 0; //接收错误计数器 volatile uint32_t txStartTime = 0; //单次发送开始的超时计时起点 // CAN错误统计 volatile uint32_t warningErrorCount = 0; //警告错误计数 volatile uint32_t passiveErrorCount = 0; //被动错误计数 volatile uint32_t busOffCount = 0; //总线关闭事件计数 volatile uint32_t rxOverflowCount = 0; //接收队列溢出计数 #define TX_TIMEOUT_MS 50 // 发送超时时间 #define MIN_TX_INTERVAL_MS 10 // 最小发送间隔(由定时器控制) //接收相关 CAN_RxHeaderTypeDef RxHeader; //接收报文头 uint8_t RxData[8]; //接收数据 volatile uint32_t rxCount = 0; volatile uint32_t rxtest = 0; //用于测试 IWDG_HandleTypeDef hiwdg; //独立看门狗句柄 /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); /* USER CODE BEGIN PFP */ void HandleCanTransmission();//// 主循环中处理CAN发送的核心函数 void PrepareCanMessage(uint8_t msgIndex);//// 准备特定索引的报文数据 void ProcessCanError(uint32_t error);//(函数已实现,但声明似乎未被使用) void CAN_Recovery_Procedure(void);//总线恢复过程状态机 void ProcessReceivedData(); //处理解析后的接收数据 void ProcessRxQueueInMainLoop(void); //在主循环中处理接收队列中的报文 static void MX_IWDG_Init(void); //初始化看门狗 // 接收队列定义 #define RX_QUEUE_SIZE 20 // typedef struct { CAN_RxHeaderTypeDef header; uint8_t data[8]; } CanRxMsg_t; CanRxMsg_t rxQueue[RX_QUEUE_SIZE]; //// 接收队列缓冲区 volatile uint16_t rxQueueHead = 0; //// 队列头指针(生产者索引,由中断修改) volatile uint16_t rxQueueTail = 0; //队列尾指针(消费者索引,由主循环修改) /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_CAN_Init();// CubeMX生成的CAN初始化(参数配置) MX_TIM2_Init();//定时器2初始化,用于产生发送周期中断 MX_IWDG_Init();//初始化独立看门狗 /* USER CODE BEGIN 2 */ //配置CAN过滤器:只接收ID为0x1003扩展帧 CAN_FilterTypeDef sFilterConfig; sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; sFilterConfig.FilterIdHigh = (0x1003 << 3)>>16; sFilterConfig.FilterIdLow = (0x1003 << 3)|(CAN_RI0R_IDE<<2)|(CAN_RI0R_RTR<<1); sFilterConfig.FilterMaskIdHigh = (0x1004 << 3)>>16;; sFilterConfig.FilterMaskIdLow = (0x1004 << 3)|(CAN_RI0R_IDE<<2)|(CAN_RI0R_RTR<<1); sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.SlaveStartFilterBank = 14; if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK) { Error_Handler(); } // // 启动CAN外设 if (HAL_CAN_Start(&hcan) != HAL_OK) { Error_Handler(); } // 激活CAN中断:发送邮箱空、错误、FIFO0收到报文 if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY | CAN_IT_ERROR | CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) { Error_Handler(); } //// 启动定时器2中断 HAL_TIM_Base_Start_IT(&htim2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { HandleCanTransmission(); // // 处理CAN发送 ProcessRxQueueInMainLoop();// 处理接收队列 // 检查总线关闭标志并启动恢复流程 if (busOffFlag && recoveryState == RECOVERY_IDLE) { recoveryState = RECOVERY_STOPPING; } // 执行恢复状态机 if (recoveryState != RECOVERY_IDLE) { CAN_Recovery_Procedure(); // ?????? } // 刷新独立看门狗,防止MCU复位 HAL_IWDG_Refresh(&hiwdg); } /* USER CODE END 3 */ } /** * @brief System Clock Configuration * @retval None */ void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /** Initializes the RCC Oscillators according to the specified parameters * in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /** Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } /* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { static uint32_t lastSendTime = 0; uint32_t currentTime = HAL_GetTick(); // 检查是否达到最小发送间隔,防止主循环处理不过来时频繁置位 if (currentTime - lastSendTime >= MIN_TX_INTERVAL_MS) { canSendFlag = 1; lastSendTime = currentTime; } } } void HAL_CAN_TxMailbox0CompleteCallback(CAN_HandleTypeDef *hcan) { __disable_irq(); txInProgress = 0; // 清除发送中标志 txSuccessCount++; // // 成功计数增加 consecutiveErrors = 0; __enable_irq(); } void HAL_CAN_TxMailbox1CompleteCallback(CAN_HandleTypeDef *hcan) { __disable_irq(); txInProgress = 0; txSuccessCount++; consecutiveErrors = 0; __enable_irq(); } void HAL_CAN_TxMailbox2CompleteCallback(CAN_HandleTypeDef *hcan) { __disable_irq(); txInProgress = 0; txSuccessCount++; consecutiveErrors = 0; __enable_irq(); } void HAL_CAN_ErrorCallback(CAN_HandleTypeDef *hcan) { uint32_t errorcode = HAL_CAN_GetError(hcan);//// 获取错误代码 __disable_irq(); txInProgress = 0;//发生错误,认为当前发送失败 txErrorCount++; if (errorcode & HAL_CAN_ERROR_EWG) { warningErrorCount++; } if (errorcode & HAL_CAN_ERROR_EPV) { passiveErrorCount++; } if (errorcode & HAL_CAN_ERROR_BOF) { busOffCount++; busOffFlag = 1;// 最重要的标志,触发总线恢复 } __enable_irq(); } void PrepareCanMessage(uint8_t msgIndex) { static uint8_t counter = 0; // ????????????? switch(msgIndex) { case 0: // ID 0x123 for (int i = 0; i < 8; i++) { txMsgConfigs[0].data[i] = counter + i; } break; case 1: // ID 0x124 for (int i = 0; i < 8; i++) { txMsgConfigs[1].data[i] = counter + 0x10 + i; // ??????? } break; // ??????????... } counter++; } void HandleCanTransmission(void) { uint32_t currentTime = HAL_GetTick(); uint8_t in_progress; __disable_irq(); in_progress = txInProgress;//// 获取发送状态 __enable_irq(); // 1. 检查超时:如果正在发送且超时,则判定为发送失败 if (in_progress && (currentTime - txStartTime > TX_TIMEOUT_MS)) { __disable_irq(); txInProgress = 0; txErrorCount++; consecutiveErrors++; uint8_t errs = consecutiveErrors; __enable_irq(); if (errs > MAX_CONSECUTIVE_ERRORS) { CAN_Recovery_Procedure(); __disable_irq(); consecutiveErrors = 0; __enable_irq(); } return; } // 2. 如果没有正在进行的发送,则检查各个报文是否到发送时间 if (!in_progress) { for (int i = 0; i < NUM_TX_MSGS; i++) { if (currentTime - txMsgConfigs[i].lastSendTime >= txMsgConfigs[i].interval) { // 检查是否有空闲邮箱 PrepareCanMessage(i); if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) > 0) { // ??TxHeader CAN_TxHeaderTypeDef TxHeader; TxHeader.StdId = txMsgConfigs[i].id; TxHeader.ExtId = 0x00; TxHeader.RTR = CAN_RTR_DATA; TxHeader.IDE = CAN_ID_STD; TxHeader.DLC = txMsgConfigs[i].dlc; TxHeader.TransmitGlobalTime = DISABLE; __disable_irq(); txInProgress = 1;//// 设置发送标志 __enable_irq(); txStartTime = currentTime; // 调用HAL库函数发送 HAL_StatusTypeDef status = HAL_CAN_AddTxMessage( &hcan, &TxHeader, txMsgConfigs[i].data, &TxMailbox ); if (status != HAL_OK) { __disable_irq(); txInProgress = 0; if (status == HAL_BUSY) { txBusyCount++; } else { txErrorCount++; consecutiveErrors++; } __enable_irq(); } else { // ????,???????? txMsgConfigs[i].lastSendTime = currentTime;//// 更新本报文发送时间 } } } } } } void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { CanRxMsg_t *nextMsg; uint16_t next_head; __disable_irq(); // 计算下一个队列头位置,并检查队列是否已满 next_head = (rxQueueHead + 1) % RX_QUEUE_SIZE; if (next_head == rxQueueTail) { rxErrorCount++; rxOverflowCount++; // ?????? __enable_irq(); // 队列已满,不得不读取并丢弃该报文,否则FIFO会阻塞 HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData); return; } __enable_irq(); // 队列未满,获取报文并存入队列 nextMsg = &rxQueue[rxQueueHead]; if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &(nextMsg->header), nextMsg->data) == HAL_OK) { __disable_irq(); rxQueueHead = next_head; // 移动头指针,完成入队 __enable_irq(); } } // ???????????? void ProcessRxQueueInMainLoop(void) { while (rxQueueTail != rxQueueHead) {//// 队列不为空 ProcessReceivedData(&rxQueue[rxQueueTail].header, rxQueue[rxQueueTail].data); rxQueueTail = (rxQueueTail + 1) % RX_QUEUE_SIZE;// 移动尾指针,出队 } } void ProcessReceivedData(CAN_RxHeaderTypeDef *header, uint8_t *data) { // // 检查是否为扩展帧且ID是0x1003 if (header->IDE == CAN_ID_EXT && header->ExtId == 0x1003) { if (data[0] == 1) { rxtest++; } // 根据数据字节0的命令类型处理 switch(data[0]) { case 0x01: // ????1 break; case 0x02: // ????2 break; // ????... default: // ???? break; } // ????????????????? // DEBUG_PRINTF("Received ID 0x1003 message, data[0]: %d\n", data[0]); } // ??????,?????????ID?? // ??????,???????? else { // ????????(????????) rxErrorCount++; } } void CAN_Recovery_Procedure(void) { uint32_t currentTime = HAL_GetTick(); switch (recoveryState) { case RECOVERY_IDLE: recoveryState = RECOVERY_STOPPING; case RECOVERY_DELAYING: if (currentTime - recoveryStartTime >= 100) { recoveryState = RECOVERY_INITIALIZING; } break; case RECOVERY_INITIALIZING: MX_CAN_Init(); // ?????CAN recoveryState = RECOVERY_CONFIGURING; break; case RECOVERY_CONFIGURING: { CAN_FilterTypeDef sFilterConfig = {0}; sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; sFilterConfig.FilterIdHigh = 0x0000; sFilterConfig.FilterIdLow = 0x0000; sFilterConfig.FilterMaskIdHigh = 0x0000; sFilterConfig.FilterMaskIdLow = 0x0000; sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; sFilterConfig.FilterActivation = ENABLE; sFilterConfig.SlaveStartFilterBank = 14; if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) == HAL_OK) { recoveryState = RECOVERY_STARTING; } break; } case RECOVERY_STARTING: if (HAL_CAN_Start(&hcan) == HAL_OK) { recoveryState = RECOVERY_ACTIVATING_NOTIFICATIONS; } break; case RECOVERY_ACTIVATING_NOTIFICATIONS: if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_TX_MAILBOX_EMPTY | CAN_IT_ERROR | CAN_IT_RX_FIFO0_MSG_PENDING) == HAL_OK) { recoveryState = RECOVERY_COMPLETE; } break; case RECOVERY_COMPLETE: // 清理状态,重置计数器,恢复完成 __disable_irq(); txInProgress = 0; consecutiveErrors = 0; txErrorCount = 0; txSuccessCount = 0; busOffFlag = 0; __enable_irq(); recoveryState = RECOVERY_IDLE; break; case RECOVERY_STOPPING: // ??????????? for (int i = 0; i < 3; i++) { HAL_CAN_AbortTxRequest(&hcan, (1 << i)); } // ??CAN??? if (HAL_CAN_Stop(&hcan) == HAL_OK) { recoveryState = RECOVERY_DELAYING; recoveryStartTime = currentTime; } else { // ??????,??????? recoveryState = RECOVERY_IDLE; // ????,???? } break; } } static void MX_IWDG_Init(void) { hiwdg.Instance = IWDG; // ???????????????? hiwdg.Init.Prescaler = IWDG_PRESCALER_32; // 32?? hiwdg.Init.Reload = 0x0FFF; // ???4095 // ???? = (32 * 4095) / 40000 ؠ3.276? if (HAL_IWDG_Init(&hiwdg) != HAL_OK) { Error_Handler(); } } /* USER CODE END 4 */ /** * @brief This function is executed in case of error occurrence. * @retval None */ void Error_Handler(void) { __disable_irq(); while (1) { // ??????????,?????? HAL_IWDG_Refresh(&hiwdg); HAL_Delay(100); // ?????? } } #ifdef USE_FULL_ASSERT /** * @brief Reports the name of the source file and the source line number * where the assert_param error has occurred. * @param file: pointer to the source file name * @param line: assert_param error line source number * @retval None */ void assert_failed(uint8_t *file, uint32_t line) { /* USER CODE BEGIN 6 */ /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ /* USER CODE END 6 */ } #endif /* USE_FULL_ASSERT */ 请详细分析上述代码,逐行详细分析
最新发布
09-16
Go 语言中,自定义错误类型是一种常见做法,用于提供更丰富的错误信息,包括错误的上下文、状态码或其他元数据。通过实现 `error` 接口(即 `Error() string` 方法),可以定义自己的错误类型。 ### 自定义错误类型的定义 一个典型的自定义错误类型如下所示: ```go type MyError struct { Code int Message string } func (e *MyError) Error() string { return e.Message } ``` 在实际使用中,可以创建该错误类型的实例,并在程序中返回: ```go func someFunction() error { return &MyError{ Code: 400, Message: "Bad Request", } } ``` ### `errors.Is` 与 `errors.As` 的区别 #### `errors.Is` `errors.Is` 用于比较两个错误是否是同一类型或是否具有相同的值。它适用于检查特定的错误值是否出现在错误链中。例如,如果使用 `fmt.Errorf` 包裹错误,并通过 `%w` 标记进行错误包装,`errors.Is` 可以穿透错误链并找到原始错误。 ```go if errors.Is(err, targetError) { // 处理特定错误 } ``` 此方法通常用于检查已知的、预定义的错误值,例如标准库中定义的错误(如 `io.EOF`)[^1]。 #### `errors.As` `errors.As` 用于检查错误链中是否存在特定类型的错误。它将错误链中的某个错误赋值给目标变量(如果存在),并返回布尔值表示是否成功匹配。这在需要访问错误的具体字段或方法时非常有用。 ```go var myErr *MyError if errors.As(err, &myErr) { fmt.Println("Error code:", myErr.Code) } ``` `errors.As` 会尝试将错误链中的某个错误转换为目标类型,并在成功匹配后将目标变量设置为该错误实例[^3]。 ### 使用场景对比 - **`errors.Is`** 更适合用于判断某个特定的错误值是否存在于错误链中,通常用于预定义错误或简单错误类型的比较。 - **`errors.As`** 更适合用于提取错误链中特定类型的错误实例,以便访问其附加信息或行为。 ### 示例代码 以下是一个完整的示例,展示如何结合自定义错误类型使用 `errors.Is` 和 `errors.As`: ```go package main import ( "errors" "fmt" ) type MyError struct { Code int Message string } func (e *MyError) Error() string { return e.Message } func main() { err := &MyError{Code: 400, Message: "Bad Request"} wrappedErr := fmt.Errorf("an error occurred: %w", err) // 使用 errors.Is 检查错误值 if errors.Is(wrappedErr, err) { fmt.Println("errors.Is matched") } // 使用 errors.As 提取特定类型的错误 var target *MyError if errors.As(wrappedErr, &target) { fmt.Printf("errors.As matched: Code=%d, Message=%s\n", target.Code, target.Message) } } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值