当前位置: 首页 > news >正文

如何提高嵌入式软件设计的代码质量

文章目录

    • 基本
      • **一、分层架构设计**
      • **二、遵循编码规范**
        • 1. **命名规范**
        • 2. **注释规范**
        • 3. **代码风格**
      • **三、内存管理优化**
        • 1. **避免动态内存分配**
        • 2. **字符串处理安全**
      • **四、错误处理与健壮性**
        • 1. **函数返回值检查**
        • 2. **防御性编程**
        • 3. **断言(Assert)**
      • **五、性能优化**
        • 1. **减少中断处理时间**
        • 2. **查表优化**
        • 3. **位操作替代乘除**
      • **六、可测试性设计**
        • 1. **依赖注入**
        • 2. **模块化设计**
      • **七、工具链辅助**
        • 1. **静态代码分析**
        • 2. **自动格式化**
        • 3. **版本控制**
      • **八、文档与注释**
        • 1. **函数文档**
        • 2. **架构文档**
      • **九、示例:LED驱动的高质量实现**
      • **十、总结**
    • 进阶
      • **一、低功耗设计优化**
        • 1. **休眠模式管理**
        • 2. **外设动态管理**
      • **二、实时性保障**
        • 1. **任务优先级分配**
        • 2. **中断延迟优化**
      • **三、可维护性增强**
        • 1. **配置参数集中管理**
        • 2. **状态机设计**
      • **四、安全防护机制**
        • 1. **内存访问保护**
        • 2. **输入验证**
      • **五、调试与诊断功能**
        • 1. **调试信息分级**
        • 2. **运行时错误捕获**
      • **六、代码可移植性**
        • 1. **平台抽象层**
        • 2. **数据类型统一**
      • **七、性能监控与调优**
        • 1. **代码执行时间测量**
        • 2. **内存使用分析**
      • **八、示例:高质量串口通信模块**
      • **九、总结**

在嵌入式软件设计中,提高代码质量需要从 架构设计、编码规范、内存管理、性能优化、可测试性等多个维度入手。以下是具体的方法和示例:

基本

一、分层架构设计

原则:将系统分为硬件抽象层(HAL)、驱动层、服务层和应用层,降低耦合度。

示例

├── HAL层(硬件抽象)
│   ├── gpio_hal.c      // GPIO底层操作
│   ├── timer_hal.c     // 定时器底层操作
│   └── ...
├── 驱动层
│   ├── led_driver.c    // LED驱动(调用HAL)
│   ├── uart_driver.c   // UART驱动
│   └── ...
├── 服务层
│   ├── message_queue.c // 消息队列服务
│   ├── event_dispatcher.c // 事件分发
│   └── ...
└── 应用层├── main.c          // 主程序├── app_logic.c     // 业务逻辑└── ...

优势

  • 硬件更换时只需修改HAL层
  • 驱动层可复用(如不同项目使用相同UART驱动)
  • 应用层专注业务逻辑,不依赖具体硬件

二、遵循编码规范

1. 命名规范
  • 匈牙利命名法(不推荐):如 int g_nCount(g_表示全局,n表示int)
  • 推荐方式
    • 变量:lower_snake_case(如 led_state
    • 函数:lower_snake_case(如 get_temperature()
    • 宏:UPPER_SNAKE_CASE(如 MAX_BUFFER_SIZE
    • 结构体:UpperCamelCase_t(如 GpioConfig_t
2. 注释规范
  • 函数头注释:说明功能、参数、返回值、注意事项
    /*** @brief 初始化LED驱动* @param pin 引脚号* @param mode 工作模式(INPUT/OUTPUT)* @return 初始化结果(0=成功,其他=失败)*/
    int led_init(uint8_t pin, GpioMode_t mode);
    
  • 行注释:解释复杂逻辑(如算法关键点)
    // 使用二分查找加速温度转换(查表法)
    int16_t temp = binary_search_lut(raw_value, temp_lut, LUT_SIZE);
    
3. 代码风格
  • 缩进:统一使用4个空格(避免Tab)
  • 括号位置:K&R风格(左括号不换行)
    if (condition) {// 代码块
    } else {// 代码块
    }
    
  • 变量声明:一行一个变量,避免混淆
    // 不推荐
    int a, b, c;// 推荐
    int a;
    int b;
    int c;
    

三、内存管理优化

1. 避免动态内存分配
  • 问题:嵌入式系统内存有限,malloc/free易导致内存碎片
  • 替代方案
    • 使用静态数组:static uint8_t buffer[256];
    • 内存池(Memory Pool):预分配固定大小内存块
      // 内存池示例
      static uint8_t pool[10][32]; // 10个32字节的内存块
      static bool pool_used[10];void* pool_alloc(size_t size) {if (size > 32) return NULL;for (int i = 0; i < 10; i++) {if (!pool_used[i]) {pool_used[i] = true;return &pool[i][0];}}return NULL; // 内存池已满
      }
      
2. 字符串处理安全
  • 使用snprintf替代sprintf防止缓冲区溢出
    char msg[32];
    snprintf(msg, sizeof(msg), "Temp: %.1f°C", temperature);
    
  • 检查字符串长度:
    if (strlen(input) < MAX_INPUT_LENGTH) {strcpy(buffer, input);
    }
    

四、错误处理与健壮性

1. 函数返回值检查
  • 调用可能失败的函数时必须检查返回值
    // 不推荐
    flash_write(address, data, len);// 推荐
    if (flash_write(address, data, len) != FLASH_SUCCESS) {handle_flash_error();
    }
    
2. 防御性编程
  • 参数合法性检查:
    void set_brightness(uint8_t value) {if (value > 100) { // 限制范围value = 100;}// 设置亮度
    }
    
  • 空指针检查:
    void process_data(uint8_t* data, size_t len) {if (data == NULL || len == 0) {return; // 避免崩溃}// 处理数据
    }
    
3. 断言(Assert)
  • 在开发阶段使用断言检测不可恢复的错误
    #ifdef DEBUG
    #define ASSERT(condition) if (!(condition)) { assert_failed(__FILE__, __LINE__); }
    #else
    #define ASSERT(condition) ((void)0)
    #endifvoid assert_failed(const char* file, uint32_t line) {// 打印错误信息并进入无限循环printf("Assertion failed at %s:%d\n", file, line);while (1);
    }// 使用示例
    void set_temp_range(int min, int max) {ASSERT(min < max); // 确保逻辑正确性// ...
    }
    

五、性能优化

1. 减少中断处理时间
  • 中断服务函数(ISR)中只做必要操作,复杂逻辑放到后台任务
    // 中断服务函数(快进快出)
    void TIM_IRQHandler(void) {static uint8_t flag = 0;flag = 1; // 设置标志TIM_ClearITPendingBit(TIMx, TIM_IT_Update);
    }// 主循环处理
    void main(void) {while (1) {if (flag) {flag = 0;process_timer_event(); // 处理定时器事件}// 处理其他任务}
    }
    
2. 查表优化
  • 用查表替代复杂计算(如三角函数、CRC校验)
    // CRC-8查表法(预计算)
    static const uint8_t crc8_table[256] = {0x00, 0x07, 0x0E, 0x09, ... // 完整表格略
    };uint8_t crc8_calculate(const uint8_t* data, size_t len) {uint8_t crc = 0;for (size_t i = 0; i < len; i++) {crc = crc8_table[crc ^ data[i]];}return crc;
    }
    
3. 位操作替代乘除
  • x << 2 替代 x * 4
  • x >> 3 替代 x / 8

六、可测试性设计

1. 依赖注入
  • 通过函数参数传入依赖对象,便于单元测试时替换
    // 原函数(依赖硬件)
    void send_data(const uint8_t* data, size_t len) {uart_send(data, len); // 直接调用硬件接口
    }// 改进:通过参数注入依赖
    void send_data(const uint8_t* data, size_t len, void (*transmit_func)(const uint8_t*, size_t)) {transmit_func(data, len); // 调用传入的函数指针
    }// 测试时传入模拟函数
    void mock_transmit(const uint8_t* data, size_t len) {// 记录数据但不实际发送
    }
    
2. 模块化设计
  • 每个模块提供独立的测试接口
    // 模块头文件增加测试函数声明(仅在测试时包含)
    #ifdef UNIT_TEST
    void test_internal_function(void); // 内部函数暴露给测试
    #endif
    

七、工具链辅助

1. 静态代码分析
  • 使用工具(如CppCheck、PC-Lint)检查代码缺陷
    # CppCheck示例
    cppcheck --enable=all --inconclusive src/
    
2. 自动格式化
  • 使用工具(如Clang-Format)统一代码风格
    # 按配置文件格式化代码
    clang-format -i -style=file src/*.c
    
3. 版本控制
  • 使用Git管理代码,遵循分支策略(如Git Flow)
    # 创建特性分支
    git checkout -b feature/new_driver
    

八、文档与注释

1. 函数文档
  • 使用Doxygen风格注释生成API文档
    /*** @brief 计算两点间距离* @param x1 第一个点的x坐标* @param y1 第一个点的y坐标* @param x2 第二个点的x坐标* @param y2 第二个点的y坐标* @return 两点间的欧几里得距离*/
    float calculate_distance(float x1, float y1, float x2, float y2);
    
2. 架构文档
  • 绘制模块关系图(如UML组件图)
  • 记录关键设计决策(如为什么选择FreeRTOS而非裸机)

九、示例:LED驱动的高质量实现

/*** @file led_driver.c* @brief LED驱动模块*/#include "led_driver.h"
#include "gpio_hal.h" // 硬件抽象层// 静态全局变量保存LED状态
static struct {uint8_t pin;bool is_on;
} leds[MAX_LEDS];/*** @brief 初始化LED驱动* @param led_id LED编号(0~MAX_LEDS-1)* @param pin GPIO引脚号* @return 初始化结果(0=成功,其他=失败)*/
int led_init(uint8_t led_id, uint8_t pin) {if (led_id >= MAX_LEDS) {return -1; // 编号超出范围}leds[led_id].pin = pin;leds[led_id].is_on = false;// 配置GPIO为输出模式return gpio_set_mode(pin, GPIO_MODE_OUTPUT);
}/*** @brief 打开指定LED* @param led_id LED编号*/
void led_on(uint8_t led_id) {if (led_id >= MAX_LEDS) {return;}gpio_write(leds[led_id].pin, GPIO_HIGH);leds[led_id].is_on = true;
}/*** @brief 关闭指定LED* @param led_id LED编号*/
void led_off(uint8_t led_id) {if (led_id >= MAX_LEDS) {return;}gpio_write(leds[led_id].pin, GPIO_LOW);leds[led_id].is_on = false;
}/*** @brief 切换LED状态* @param led_id LED编号*/
void led_toggle(uint8_t led_id) {if (led_id >= MAX_LEDS) {return;}if (leds[led_id].is_on) {led_off(led_id);} else {led_on(led_id);}
}

十、总结

提高嵌入式代码质量需要从设计、编码、测试三个阶段入手:

  1. 设计阶段:分层架构、模块化、降低耦合
  2. 编码阶段:遵循规范、优化内存、防御性编程
  3. 测试阶段:单元测试、静态分析、持续集成

通过以上方法,可以显著提高代码的可靠性、可维护性和可扩展性,减少后期维护成本。

进阶

在嵌入式编程中,还有许多细节和技术可以进一步提升代码质量。以下是一些补充建议,涵盖低功耗设计、实时性保障、代码可维护性、安全防护等多个维度:

一、低功耗设计优化

1. 休眠模式管理
  • 设计状态机控制设备进入不同功耗模式:
    typedef enum {POWER_MODE_ACTIVE,     // 活动模式POWER_MODE_SLEEP,      // 浅睡眠(外设关闭)POWER_MODE_DEEPSLEEP   // 深度睡眠(CPU暂停)
    } PowerMode_t;void set_power_mode(PowerMode_t mode) {switch (mode) {case POWER_MODE_SLEEP:disable_peripherals();enter_sleep_mode();break;case POWER_MODE_DEEPSLEEP:save_context();disable_all_interrupts();enter_deep_sleep_mode();break;default:// 活动模式无需特殊操作break;}
    }
    
2. 外设动态管理
  • 按需开启/关闭外设,降低静态功耗:
    void uart_transfer_complete(void) {// 传输完成后关闭UART外设UART_Disable(UART1);set_power_mode(POWER_MODE_SLEEP);
    }
    

二、实时性保障

1. 任务优先级分配
  • 基于FreeRTOS的任务优先级设计:
    // 任务优先级定义(数值越大优先级越高)
    #define PRIORITY_HIGH       (3)
    #define PRIORITY_MEDIUM     (2)
    #define PRIORITY_LOW        (1)// 创建高优先级任务(如传感器数据采集)
    xTaskCreate(Task_SensorRead, "Sensor", 256, NULL, PRIORITY_HIGH, NULL);// 创建低优先级任务(如数据显示)
    xTaskCreate(Task_DisplayUpdate, "Display", 256, NULL, PRIORITY_LOW, NULL);
    
2. 中断延迟优化
  • 减少中断服务函数(ISR)执行时间:
    // 快速ISR(仅设置标志)
    void EXTI0_IRQHandler(void) {static bool key_pressed = false;key_pressed = true;EXTI_ClearITPendingBit(EXTI_Line0);
    }// 后台任务处理按键事件
    void Task_KeyProcessing(void *pvParameters) {for (;;) {if (key_pressed) {key_pressed = false;process_key_event(); // 复杂处理放在任务中}vTaskDelay(pdMS_TO_TICKS(10));}
    }
    

三、可维护性增强

1. 配置参数集中管理
  • 使用配置结构体替代分散的宏定义:
    // 不推荐:分散的宏定义
    #define UART_BAUDRATE 115200
    #define UART_DATABITS 8
    #define UART_PARITY_NONE// 推荐:配置结构体
    typedef struct {uint32_t baudrate;uint8_t databits;ParityType parity;StopBitsType stopbits;
    } UART_Config_t;// 全局配置实例
    const UART_Config_t uart_config = {.baudrate = 115200,.databits = 8,.parity = PARITY_NONE,.stopbits = STOPBITS_1
    };
    
2. 状态机设计
  • 使用枚举和函数指针实现清晰的状态转换:
    typedef enum {STATE_INIT,STATE_IDLE,STATE_WORKING,STATE_ERROR
    } SystemState_t;// 状态处理函数类型
    typedef void (*StateHandler)(void);// 状态处理函数表
    static const StateHandler state_handlers[] = {[STATE_INIT] = handle_init,[STATE_IDLE] = handle_idle,[STATE_WORKING] = handle_working,[STATE_ERROR] = handle_error
    };// 状态机主循环
    void state_machine_run(void) {static SystemState_t current_state = STATE_INIT;// 执行当前状态处理函数if (state_handlers[current_state] != NULL) {state_handlers[current_state]();}// 根据条件转换状态if (should_transition()) {current_state = get_next_state();}
    }
    

四、安全防护机制

1. 内存访问保护
  • 使用内存屏障(Memory Barrier)确保数据一致性:
    // 写入操作后插入内存屏障,确保数据同步到内存
    void write_critical_data(uint32_t* address, uint32_t value) {*address = value;__asm__ volatile ("dsb sy" : : : "memory"); // 数据同步屏障
    }// 读取操作前插入内存屏障,确保读取最新数据
    uint32_t read_critical_data(uint32_t* address) {__asm__ volatile ("isb sy" : : : "memory"); // 指令同步屏障return *address;
    }
    
2. 输入验证
  • 严格验证外部输入(如通信协议数据):
    // 解析命令帧(防止缓冲区溢出和越界访问)
    bool parse_command(const uint8_t* buffer, size_t length) {if (length < MIN_COMMAND_LENGTH) {return false; // 长度不足}CommandType cmd = buffer[0];uint8_t param_length = buffer[1];if (param_length + 2 > length) { // 包含命令头的总长度检查return false; // 参数长度超出缓冲区}// 安全地处理参数const uint8_t* params = &buffer[2];process_command(cmd, params, param_length);return true;
    }
    

五、调试与诊断功能

1. 调试信息分级
  • 实现可配置的日志系统:
    typedef enum {LOG_LEVEL_ERROR = 0,LOG_LEVEL_WARNING,LOG_LEVEL_INFO,LOG_LEVEL_DEBUG
    } LogLevel_t;// 当前日志级别(可通过配置修改)
    static LogLevel_t current_log_level = LOG_LEVEL_INFO;void log_message(LogLevel_t level, const char* format, ...) {if (level > current_log_level) {return; // 级别低于当前设置,不输出}// 格式化并输出日志va_list args;va_start(args, format);vprintf(format, args);va_end(args);
    }// 使用示例
    log_message(LOG_LEVEL_ERROR, "Flash write failed at address 0x%08X\n", address);
    
2. 运行时错误捕获
  • 实现硬件错误处理(如总线错误、内存访问错误):
    // 硬件错误处理函数
    void HardFault_Handler(void) {// 保存错误上下文save_fault_context();// 进入错误恢复模式或重启系统enter_error_recovery();
    }
    

六、代码可移植性

1. 平台抽象层
  • 使用条件编译隔离平台差异:
    // 平台相关函数声明
    #ifdef TARGET_STM32
    void platform_init(void) {stm32_clock_init();stm32_gpio_init();
    }
    #elif defined(TARGET_NRF52)
    void platform_init(void) {nrf52_clock_init();nrf52_gpio_init();
    }
    #endif// 平台无关代码
    void system_init(void) {platform_init(); // 调用平台相关初始化// 其他初始化...
    }
    
2. 数据类型统一
  • 使用C99标准数据类型(如uint8_tint32_t)替代原生类型:
    // 不推荐:依赖平台的原生类型
    unsigned char data; // 长度可能因平台而异// 推荐:明确长度的数据类型
    uint8_t data; // 明确为8位无符号整数
    

七、性能监控与调优

1. 代码执行时间测量
  • 使用高精度定时器测量关键代码段执行时间:
    uint32_t start_time, end_time;// 开始计时
    start_time = DWT->CYCCNT; // Cortex-M的周期计数器// 执行关键代码
    process_sensor_data();// 结束计时
    end_time = DWT->CYCCNT;
    uint32_t elapsed_cycles = end_time - start_time;
    float elapsed_ms = (float)elapsed_cycles / SystemCoreClock * 1000.0f;log_message(LOG_LEVEL_DEBUG, "Data processing took %.2f ms\n", elapsed_ms);
    
2. 内存使用分析
  • 实现内存使用统计功能:
    // 内存使用统计结构体
    typedef struct {uint32_t total_allocated;uint32_t max_allocated;uint32_t current_used;
    } MemoryStats_t;// 自定义内存分配函数
    void* my_malloc(size_t size) {void* ptr = malloc(size);if (ptr) {// 更新内存统计memory_stats.total_allocated += size;if (memory_stats.total_allocated > memory_stats.max_allocated) {memory_stats.max_allocated = memory_stats.total_allocated;}memory_stats.current_used += size;}return ptr;
    }// 自定义内存释放函数
    void my_free(void* ptr, size_t size) {if (ptr) {free(ptr);memory_stats.current_used -= size;}
    }
    

八、示例:高质量串口通信模块

/*** @file uart_driver.c* @brief 高性能、可靠的UART驱动*/#include "uart_driver.h"
#include "ring_buffer.h" // 环形缓冲区实现// 每个UART实例的配置和状态
typedef struct {UART_HandleTypeDef* huart;RingBuffer_t rx_buffer;uint8_t rx_data[UART_RX_BUFFER_SIZE];uint8_t tx_data[UART_TX_BUFFER_SIZE];bool tx_busy;
} UART_Instance_t;// 支持多个UART实例
static UART_Instance_t uart_instances[UART_MAX_INSTANCES];/*** @brief 初始化UART实例* @param instance 实例编号* @param huart HAL库UART句柄* @return 初始化结果(0=成功,其他=失败)*/
int uart_init(uint8_t instance, UART_HandleTypeDef* huart) {if (instance >= UART_MAX_INSTANCES || huart == NULL) {return -1;}// 初始化实例结构体UART_Instance_t* uart = &uart_instances[instance];uart->huart = huart;uart->tx_busy = false;// 初始化环形缓冲区ring_buffer_init(&uart->rx_buffer, uart->rx_data, UART_RX_BUFFER_SIZE);// 启动DMA接收(非阻塞)if (HAL_UART_Receive_DMA(huart, uart->rx_data, UART_RX_BUFFER_SIZE) != HAL_OK) {return -2;}return 0;
}/*** @brief 发送数据(非阻塞)* @param instance 实例编号* @param data 数据指针* @param length 数据长度* @return 发送结果(0=成功,其他=失败)*/
int uart_send(uint8_t instance, const uint8_t* data, uint32_t length) {if (instance >= UART_MAX_INSTANCES || data == NULL || length == 0) {return -1;}UART_Instance_t* uart = &uart_instances[instance];// 检查是否有正在进行的发送if (uart->tx_busy) {return -2; // 发送忙}// 复制数据到发送缓冲区(避免数据覆盖)if (length > UART_TX_BUFFER_SIZE) {length = UART_TX_BUFFER_SIZE; // 防止溢出}memcpy(uart->tx_data, data, length);// 启动DMA发送uart->tx_busy = true;if (HAL_UART_Transmit_DMA(uart->huart, uart->tx_data, length) != HAL_OK) {uart->tx_busy = false;return -3;}return 0;
}/*** @brief 获取接收到的数据长度* @param instance 实例编号* @return 接收缓冲区中的数据长度*/
uint32_t uart_available(uint8_t instance) {if (instance >= UART_MAX_INSTANCES) {return 0;}UART_Instance_t* uart = &uart_instances[instance];return ring_buffer_available(&uart->rx_buffer);
}/*** @brief 读取接收到的数据* @param instance 实例编号* @param buffer 数据缓冲区* @param max_length 最大读取长度* @return 实际读取的字节数*/
uint32_t uart_read(uint8_t instance, uint8_t* buffer, uint32_t max_length) {if (instance >= UART_MAX_INSTANCES || buffer == NULL || max_length == 0) {return 0;}UART_Instance_t* uart = &uart_instances[instance];return ring_buffer_read(&uart->rx_buffer, buffer, max_length);
}/*** @brief UART接收完成回调(由HAL库调用)* @param huart UART句柄*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart) {// 查找对应的UART实例for (uint8_t i = 0; i < UART_MAX_INSTANCES; i++) {if (uart_instances[i].huart == huart) {// 接收完成,继续下一次接收HAL_UART_Receive_DMA(huart, uart_instances[i].rx_data, UART_RX_BUFFER_SIZE);break;}}
}/*** @brief UART发送完成回调(由HAL库调用)* @param huart UART句柄*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef* huart) {// 查找对应的UART实例for (uint8_t i = 0; i < UART_MAX_INSTANCES; i++) {if (uart_instances[i].huart == huart) {// 发送完成,标记为不忙uart_instances[i].tx_busy = false;break;}}
}

九、总结

提高嵌入式代码质量是一个系统性工程,需要从多个维度综合考虑:

  1. 架构层面:分层设计、模块化、低耦合高内聚
  2. 代码实现:遵循规范、优化内存、增强安全性
  3. 运行特性:保障实时性、降低功耗、提高可靠性
  4. 可维护性:状态机设计、配置集中化、良好注释
  5. 开发流程:单元测试、静态分析、持续集成

通过以上这些方法,可以构建出更加健壮、高效、易维护的嵌入式系统,减少后期维护成本和潜在风险。

http://www.dtcms.com/a/199439.html

相关文章:

  • 用 CodeBuddy 实现「IdeaSpark 每日灵感卡」:一场 UI 与灵感的极简之旅
  • MathType公式如何按照(1)(2)…编号
  • PYTHON训练营DAY30
  • Windows多功能工具箱软件推荐
  • Linux配置SSH密钥认证
  • 2025年新发布的 基于鸿蒙操作系统5的 电脑可以支持Windows 应用嘛?
  • android13以太网静态ip不断断开连上问题
  • 什么业务需要用到waf
  • vue2.0 的计算属性
  • esp32课设记录(三)mqtt通信记录 附mqtt介绍
  • 软件工程-项目管理
  • 【android bluetooth 协议分析 01】【HCI 层介绍 8】【ReadLocalVersionInformation命令介绍】
  • CVE-2015-3934 Fiyo CMS SQL注入
  • 词嵌入基础
  • el-tree结合el-tree-transfer实现穿梭框里展示树形数据
  • 【android bluetooth 协议分析 01】【HCI 层介绍 7】【ReadLocalName命令介绍】
  • Feature Toggle 不再乱:如何设计一个干净、安全、可控的特性开关系统?
  • LeetCode 39. 组合总和 LeetCode 40.组合总和II LeetCode 131.分割回文串
  • 模板(template)初始
  • Spring Cloud Seata 深度解析:原理与架构设计
  • 微店平台关键字搜索商品接口技术实现
  • 题海拾贝:P2910 [USACO08OPEN] Clear And Present Danger S
  • kotlin Android AccessibilityService 无障碍入门
  • UE RPG游戏开发练手 第二十八课 重攻技能1
  • k8s节点维护的细节
  • 带你搞懂@Valid和@Validated的区别
  • 线代第三章向量第一节:n维向量及其运算
  • Electron + Vite + Vue 项目中的 IPC 通信三层封装实践
  • 解决RAGFlow部署中镜像源拉取的问题
  • vi实时查看日志