链载Ai

标题: 从谨慎检查到一键接受,TRAE 如何成为我的主力 IDE? [打印本页]

作者: 链载Ai    时间: 昨天 13:56
标题: 从谨慎检查到一键接受,TRAE 如何成为我的主力 IDE?


本文作者:Ida,对移动端开发拥有深厚的技术积累。近年来,积极探索如何利用 AI 技术提升日常工作效率,并尝试将生成式 AI 应用于研发流程优化。曾参与多个复杂项目的开发与维护,擅长将创新技术融入实际场景,提升用户体验。热爱分享技术心得,致力于让更多人了解并掌握前沿科技。


在 TRAE 中国版正式发布之后,我便有意识地用它来解决日常开发中遇到的各种问题。从一开始谨慎 check 每一条变更再决定是否接受,到现在点击全部接受的概率越来越高,TRAE 正在变成我写代码的主力 IDE。


希望本文能给从事客户端开发的同学在 TRAE 的使用上提供一些启发和参考,如果大家有好的实践探索也欢迎评论交流。


用 TRAE 打开工程文件


不知道大家用 TRAE 开发客户端功能时一般都是把哪个文件夹丢给 TRAE 呢?


开发业务需求时可以用 TRAE 直接打开工程中源码所在的文件夹,这样基本用到的文件都能在 TRAE 里索引到,也避免了整个工程下其他文件的干扰。


如果是明确针对某个文件夹下的代码分析或修改,有时也会直接用 TRAE 打开该特定文件夹,以便它可以快速建立代码索引,精确地去分析上下文。


但也要注意,TRAE 的历史会话记录是以文件夹维度来存储的,一旦文件夹被删除,之前在该文件夹中发生的历史会话也就全部丢失了!


关于导出历史会话记录,可以用魔法打败魔法,直接让 AI 把会话内容整理到文件中,一句下面这样的提示词即可:


将上面的对话历史内容全部整理到该文件中,包括给出的代码片段,不要遗漏内容,优化输出的markdown格式,让问题和回答一目了然


实现一个新功能模块


对于一个新功能模块,个人认为是非常适合用 TRAE 来完成绝大部分代码的,对于新增类的实现和完善,几乎都可以由 AI 来完成。最近的业务中正好就是这样一个例子:全新的功能,与现有业务代码的耦合度较低,实践下来大约有 90% 的代码可以用 AI 生成的,使用的 LLM 主要是:Gemini-2.5-Pro-Preview(0506)(最新版本中已经是 Gemini-2.5-Pro)和 DeepSeek-V3-0324。


下面列举一些实现过程中的典型 Case:


Case 1:用类图先搭建起功能模块的基础框架


我一般习惯在技术方案中就把实现一个功能模块所需的类都设计好,然后再开始写代码,现在有了这份类图的指导,就可以使用 TRAE 快速地把关键类的基础框架搭建起来了。


XXX模块的最初类设计图


如果方法和属性定义的比较清晰,可以直接将该类图截图给 Gemini 作为上下文,节省写提示词的时间,然后在对话时使用 @Builder 直接生成类的文件。每轮对话最好只处理一个文件,然后针对这个类的实现提具体的要求,例如:


上下滑动查看完整内容

import Foundation
classXXXStateManager {
// MARK: - SingletonstaticletsharedInstance = XXXStateManager()privateinit(){}
// MARK: - Propertiesprivatevar_value: Float =0.0privateletlock= NSLock()
varvalue: Float { get{ lock.lock() defer {lock.unlock() }return_value }set{ lock.lock() _value = newValue lock.unlock()// 通知外部积分已变更,会携带此时的value和state值 } }
// MARK: - Methods
///获取百分比 funcgetXXXValue() -> Float{//TODO:Implementationreturnvalue }
///根据百分比返回当前状态枚举值 funcgetXXXState()/*-> XXXState*/{//TODO:Implementation & Define XXXState enum }
///请求接口,更新积分 funcrequestUpdateXXXValue(){//TODO:Implementation }
///请求接口,获取最新积分 funcsyncXXXValue(){//TODO:Implementation }
///开启后,重置积分 funcresetXXXToValue(_ newValue: Float){//TODO:Implementation self.value= newValue }}
//TODOefine XXXState enum if needed, for example:// enum XXXState {// case stateA// case stateB// // ... other states// }


之后在实现每个类的过程中,就是不断地拆解问题,让 AI 去完成,一个问题解决后最好再开一个新的会话解决下一个问题,避免 LLM 对上下文理解的干扰,记得当新增代码量到一定程度时要查看是否有编译错误。


左右滑动查看更多精彩内容


Case 2: 用 Figma 中的 UIKit 代码生成视图代码


以前这种画 UI 又枯燥又磨人的活,终于可以在几分钟内就搞定了~


我在这里的做法是,在 Figma 中选中图层,将对应的 UIKit 代码直接复制到 TRAE 中让 AI 去生成 Swift 代码。先实现视图本身的样式,再往里添加子视图元素,生成的代码基本不用做太大改动。



上下滑动查看完整内容

importUIKitimportSnapKit
classReadyCardView:UIView{
privateleticonView=UIView()privatelettitleLabel=UILabel()privateletsubTitleLabel=UILabel()
overrideinit(frame:CGRect) { super.init(frame: frame) setupView() setupSubviews() }
requiredinit?(coder:NSCoder) { super.init(coder: coder) setupView() setupSubviews() }
privatefuncsetupView() {// 设置视图基本属性 self.clipsToBounds=false self.layer.cornerRadius=16
// 添加阴影效果 letshadowPath=UIBezierPath(roundedRect: bounds, cornerRadius:16) layer.shadowPath=shadowPath.cgPath layer.shadowColor=UIColor(red:0, green:0, blue:0, alpha:0.25).cgColor layer.shadowOpacity=1 layer.shadowRadius=16 layer.shadowOffset=CGSize(width:0, height:8)
// 添加渐变背景 letgradientLayer=CAGradientLayer() gradientLayer.colors=[ UIColor(red:0, green:0, blue:0, alpha:1).cgColor, UIColor(red:0, green:0, blue:0, alpha:1).cgColor, UIColor(red:0, green:0.267, blue:0.209, alpha:1).cgColor ] gradientLayer.locations=[0,0.6,1] gradientLayer.startPoint=CGPoint(x:0, y:0.5) gradientLayer.endPoint=CGPoint(x:1, y:0.5) gradientLayer.frame=bounds gradientLayer.cornerRadius=16 layer.insertSublayer(gradientLayer, at:0)
// 添加边框 layer.borderWidth=1.5 layer.borderColor=UIColor(red:1, green:1, blue:1, alpha:0.15).cgColor }
privatefuncsetupSubviews() {// 添加iconView addSubview(iconView) iconView.snp.makeConstraints { makein make.width.height.equalTo(120) make.top.equalToSuperview().offset(64) make.centerX.equalToSuperview() }
// 添加titleLabel addSubview(titleLabel) titleLabel.textColor=UIColor(red:1, green:1, blue:1, alpha:1) titleLabel.font=UIFont(name:"ingFangSC-Medium", size:20) titleLabel.textAlignment=.center titleLabel.text="已就绪" titleLabel.snp.makeConstraints { makein make.width.equalTo(210) make.height.equalTo(28) make.top.equalTo(iconView.snp.bottom).offset(8) make.centerX.equalToSuperview() }
// 添加subTitleLabel addSubview(subTitleLabel) subTitleLabel.textColor=UIColor(red:0.604, green:0.604, blue:0.604, alpha:1) subTitleLabel.font=UIFont(name:"ingFangSC-Regular", size:15) subTitleLabel.text="" subTitleLabel.snp.makeConstraints { makein make.width.equalTo(270) make.height.equalTo(21) make.top.equalTo(titleLabel.snp.bottom) make.centerX.equalToSuperview() } }
overridefunclayoutSubviews() { super.layoutSubviews()// 更新渐变层frameifletgradientLayer=layer.sublayers?.firstas?CAGradientLayer{ gradientLayer.frame=bounds } }}


这种方式简单直接,零成本直接上手使用。更进一步,我们也可以通过 Figma 官方提供的 MCP 工具来实现 UI 代码的生成,具体操作会在进阶篇中介绍。


Case 3: 生成读取 Settings 配置的代码


对于读取 Settings 配置的代码也是比较模板化的,如果手动去敲,在配置项很多的时候也要花费一些时间,现在描述清楚要求后,静待 TRAE 来生成代码就行了。下面左侧是生成的代码效果,几乎不需要改什么可以直接用。之后可以继续让 TRAE 去用这份 Configuration 替换掉写死在代码中的值。


上下滑动查看完整内容

importFoundationimportAWEAppSettings// 请确保已正确导入 AWEAppSettings
/// XXX铃功能相关配置classXXXConfiguration {// MARK: - Settings Keys/// 灵动岛文案配置的根键privatestaticletkXXXTextConfigRootKey="XXX_text_config"/// 作品播放x毫秒后的互动才能算积分(单位毫秒)的键privatestaticletkXXXPlayDurationMinKey="XXX_play_duration_min"/// XXX横幅自动展示x秒后消失(单位秒)的键privatestaticletkXXXBannerShowDurationKey="XXX_banner_show_duration"/// 点击发现tab后,x秒内不重复请求接口(单位秒)的键privatestaticletkXXXClickTabRequestIntervalKey="XXX_click_tab_request_interval"/// 前x次点击小铃铛(uid/did维度都可)会展开横幅(x=3)的键privatestaticletkXXXClickMatchBellExpandBannerCountKey="XXX_click_match_bell_expand_banner_count"// MARK: - Sub Keys for XXX_text_configprivatestaticletkTextConfigEntranceKey="entrance"privatestaticletkTextConfigCollectKey="collect"privatestaticletkTextConfigCollectTitleKey="title"// 嵌套在 collect 下privatestaticletkTextConfigCollectSubTitleKey="sub_title"// 嵌套在 collect 下privatestaticletkTextConfigFullKey="full"privatestaticletkTextConfigFullTitleKey="title"// 嵌套在 full 下privatestaticletkTextConfigFullSubTitleKey="sub_title"// 嵌套在 full 下privatestaticletkTextConfigFullBtnTextKey="btn_text"// 嵌套在 full 下// MARK: - Default Values for XXX_text_configprivatestaticletdefaultTextConfigEntrance=""privatestaticletdefaultTextConfigCollectTitle=""privatestaticletdefaultTextConfigCollectSubTitle=""privatestaticletdefaultTextConfigFullTitle=""privatestaticletdefaultTextConfigFullSubTitle=""privatestaticletdefaultTextConfigFullBtnText=""privatestaticletdefaultCollectDictionary: [String:String]=[ kTextConfigCollectTitleKey: defaultTextConfigCollectTitle, kTextConfigCollectSubTitleKey: defaultTextConfigCollectSubTitle ]privatestaticletdefaultFullDictionary: [String:String]=[ kTextConfigFullTitleKey: defaultTextConfigFullTitle, kTextConfigFullSubTitleKey: defaultTextConfigFullSubTitle, kTextConfigFullBtnTextKey: defaultTextConfigFullBtnText ]// MARK: - General Settings Getters/// 获取作品播放x毫秒后的互动才能算积分(单位毫秒)/// 默认值: 3000staticfuncplayDurationMin() ->Int{returnAWEAppSettings.awemeSettings().integer(forKeyPath: kXXXPlayDurationMinKey, defaultValue:3000) }/// 获取XXX横幅自动展示x秒后消失(单位秒)/// 默认值: 10staticfuncbannerShowDuration() ->Int{returnAWEAppSettings.awemeSettings().integer(forKeyPath: kXXXBannerShowDurationKey, defaultValue:10) }/// 获取点击发现tab后,x秒内不重复请求接口(单位秒)/// 默认值: 30staticfuncclickTabRequestInterval() ->Int{returnAWEAppSettings.awemeSettings().integer(forKeyPath: kXXXClickTabRequestIntervalKey, defaultValue:30) }/// 获取前x次点击小铃铛(uid/did维度都可)会展开横幅(x=3)/// 默认值: 3staticfuncclickMatchBellExpandBannerCount() ->Int{returnAWEAppSettings.awemeSettings().integer(forKeyPath: kXXXClickMatchBellExpandBannerCountKey, defaultValue:3) }// MARK: - Text Config Getters/// 通用方法:获取 XXX_text_config 下指定子键的字符串值/// - Parameters:/// - subKey: XXX_text_config 内的子键 (例如 "entrance" 或 "collect.title")/// - defaultValue: 如果未找到配置,则返回的默认值/// - Returns: 配置的字符串值或默认值privatestaticfuncgetTextConfigStringValue(forKeysubKey:String,defaultValue:String) ->String{ letkeyPath="\(kXXXTextConfigRootKey).\(subKey)"returnAWEAppSettings.awemeSettings().string(forKeyPath: keyPath, defaultValue: defaultValue)??defaultValue }
/// 通用方法:获取 XXX_text_config 下指定子键的字典值/// - Parameters:/// - subKey: XXX_text_config 内的子键 (例如 "collect" 或 "full")/// - defaultValue: 如果未找到配置,则返回的默认值/// - Returns: 配置的字典值或默认值privatestaticfuncgetTextConfigDictionaryValue(forKeysubKey:String,defaultValue: [String:Any]) -> [String:Any] { letkeyPath="\(kXXXTextConfigRootKey).\(subKey)"returnAWEAppSettings.awemeSettings().dictionary(forKeyPath: keyPath, defaultValue: defaultValue)??defaultValue }/// 获取灵动岛文案配置 - 入口文案/// 例如:"entrance":""/// 默认值: ""staticfunctextConfigEntrance() ->String{returngetTextConfigStringValue(forKey: kTextConfigEntranceKey, defaultValue: defaultTextConfigEntrance) }// MARK: - Collect Text Config
/// 获取灵动岛文案配置 - "collect" 对象字典/// 默认值: `["title": "", "sub_title": ""]`staticfunctextConfigCollectDictionary() -> [String:String] { letdict=getTextConfigDictionaryValue(forKey: kTextConfigCollectKey, defaultValue: defaultCollectDictionary)returndictas?[String:String]??defaultCollectDictionary }/// 获取灵动岛文案配置 - 充能中标题/// 例如:"collect.title":"XXX铃充能中"/// 默认值: "XXX铃充能中"staticfunctextConfigCollectTitle() ->String{ letkeyPath="\(kTextConfigCollectKey).\(kTextConfigCollectTitleKey)"returngetTextConfigStringValue(forKey: keyPath, defaultValue: defaultTextConfigCollectTitle) }/// 获取灵动岛文案配置 - 充能中副标题/// 例如:"collect.sub_title":""/// 默认值: ""staticfunctextConfigCollectSubTitle() ->String{ letkeyPath="\(kTextConfigCollectKey).\(kTextConfigCollectSubTitleKey)"returngetTextConfigStringValue(forKey: keyPath, defaultValue: defaultTextConfigCollectSubTitle) }// MARK: - Full Text Config (寻找已就绪)
/// 获取灵动岛文案配置 - "full" 对象字典/// 默认值: `["title": "寻找已就绪", "sub_title": "点击开启,找到与你有默契的人", "btn_text": "开启寻找"]`staticfunctextConfigFullDictionary() -> [String:String] { letdict=getTextConfigDictionaryValue(forKey: kTextConfigFullKey, defaultValue: defaultFullDictionary)returndictas?[String:String]??defaultFullDictionary }/// 获取灵动岛文案配置 - 寻找已就绪标题/// 例如:"full.title":""/// 默认值: ""staticfunctextConfigFullTitle() ->String{ letkeyPath="\(kTextConfigFullKey).\(kTextConfigFullTitleKey)"returngetTextConfigStringValue(forKey: keyPath, defaultValue: defaultTextConfigFullTitle) }/// 获取灵动岛文案配置 - 寻找已就绪副标题/// 例如:"full.sub_title":""/// 默认值: ""staticfunctextConfigFullSubTitle() ->String{ letkeyPath="\(kTextConfigFullKey).\(kTextConfigFullSubTitleKey)"returngetTextConfigStringValue(forKey: keyPath, defaultValue: defaultTextConfigFullSubTitle) }/// 获取灵动岛文案配置 - 寻找已就绪按钮文案/// 例如:"full.btn_text":""/// 默认值: ""staticfunctextConfigFullBtnText() ->String{ letkeyPath="\(kTextConfigFullKey).\(kTextConfigFullBtnTextKey)"returngetTextConfigStringValue(forKey: keyPath, defaultValue: defaultTextConfigFullBtnText) }}


Case 4: 随时对代码进行优化


在实现功能的过程中,可以借助 TRAE 随时对代码进行优化。比如这次开发中,异步读写 storage 在两处都有同样的处理,我让 AI 直接封装了一个AsyncStorageUtil类。再比如,我就是看某段代码写的很啰嗦,感觉可以优化,但是没有思路,也可以直接让 AI 给优化建议。



Case 5: 将 OC 代码翻译成 Swift


把大段的 OC 代码手动翻译成 Swift 还是会费一番功夫的,现在 AI 可以秒完成,在我们写 Swift 时一定要利用上。



Case 6: 使用 TRAE 的 cue 功能直接写代码


除了在对话框中与 AI 沟通来完成任务,很多时候我也会直接在 TRAE 中写代码的,按 Tab 键即可接受预测的内容,按 ESC 键取消当前的预测,体验下来接受的概率还是比较高的,这种方式也推荐大家去试用。


分析已有的功能模块


Case1: Copy 代码时补齐头文件


在开发中我们会遇到的一种情况是,去已有代码里 Copy 一段逻辑到自己的代码里,有时这段代码需要额外引入很多头文件,纯靠记忆精准敲出这些头文件是有一点难为我胖虎了,去原来的代码一点点找又比较费时,尤其是那个文件引入了一屏幕都放不下的头文件时。


此时就可以让 AI 先去干一番活啦,比如我要把下面的代码复制到一个新文件中去:



Case2: 分析长函数的执行过程


当我们遇到不能一眼看出某个方法在干什么的时候,可以让 LLM 详细解释这个方法,并生成流程图,比如下面这段代码,我们让 Gemini 来分析解释一下,并让它生成可以在 Mermaid 或 draw.io 中展示的流程图:


上下滑动查看完整内容

-(void)fetchRecommendCardOfFamiliarFeedWithSuccessCompletionvoid(^)(BOOL))completion{if(![selfenableInsertFamiliarFeedCard]){AWEBLOCK_INVOKE(completion,NO);return;}dispatch_group_tgroup=dispatch_group_create();BOOLfetchDataSuccess=NO;if([self.familiarFeedRecommendStrategyenableInsertFamiliarFeedObdCardAB]&&self.familiarFeedObdDataSource.models.count==0){fetchDataSuccess=YES;dispatch_group_enter(group);[selffetchFamiliarRecommendUserDataWithType:AWERecommendTypeFeedObdCardisAutoTrigger:NOcompletion:^{dispatch_group_leave(group);}];}if([self.familiarFeedRecommendStrategyenableInsertFamiliarFeedMateCardAB]&&self.familiarFeedMateDataSource.models.count==0){fetchDataSuccess=YES;dispatch_group_enter(group);[selffetchFamiliarRecommendUserDataWithType:AWERecommendTypeFeedMateCardisAutoTrigger:NOcompletion:^{dispatch_group_leave(group);}];}if([self.familiarFeedRecommendStrategyenableInsertFamiliarFeedListCardAB]&&self.familiarFeedMultiMateDataSource.models.count==0){fetchDataSuccess=YES;dispatch_group_enter(group);[selffetchFamiliarRecommendUserDataWithType:AWERecommendTypeFeedMateMultiCardisAutoTrigger:NOcompletion:^{dispatch_group_leave(group);}];}if(fetchDataSuccess){dispatch_group_notify(group,dispatch_get_main_queue(),^{AWEBLOCK_INVOKE(completion,YES);});}else{AWEBLOCK_INVOKE(completion,NO);}}


左右滑动查看更多精彩内容


可以从 TRAE 的插件市场中安装一个Mermaid Preview插件,然后创建一个 mermaid.md,把代码贴过去就可以预览效果了,会根据当前的IDE给对应的配色。


```mermaid//文件中加上该表示//直接将mermaid源码粘贴在这里


Case3: 分析一个完整的功能模块


在做技术调研时也经常会遇到要去了解一个完整功能模块实现过程,如果这个模块的代码比较内聚,并且都集中放在一个文件夹内,那就可以把整个文件夹丢给 AI 去分析,并让它生成流程图和时序图,但也要考虑到 LLM 一次可以接收的上下文长度。比如之前调研主框架的异形卡实现过程:



这样了解一番后,对这个模块是怎么工作的就有一个大致的了解,提升我们调研的效率。


Case4: 辅助完成代码重构


在做代码重构时,也可以让 TRAE 来辅助完成,比如一些文件重名替换、抽离定义常量等等。前段时间把一个气泡视图重构成了可以给其他场景使用的通用组件,也是用 TRAE 完成了大部分工作,基本上自己想好了重构方案后,就是指挥 AI 来一步步干活了。


我这里直接新开一个 TRAE 窗口,只打开要修改的文件夹,然后就可以引用#Workspace了,也避免了其他代码上下文对 LLM 分析的干扰。



AI 修改后需要再审查核对一下,整体上比纯人工来写提效了很多,在我这次重构中至少节约 1 pd。







欢迎光临 链载Ai (https://www.lianzai.com/) Powered by Discuz! X3.5