代码
快来分享你的内容吧~
- 2025-12-30·后端对于个人开发者来说,这类工具可以帮你在提交代码前发现潜在问题,拿来检测一下自己之前的老项目代码也是不错的;对于团队来说,可以把 AI 接入到 CI/CD 流程中,自动化代码安全检查。查看全文加油鸭:DeepAudit这个项目太有潜力了!用AI做代码审计,还能导出专业报告,简直是开发者的安全助手,期待它越来越精准!925分享
- 2024-09-03·后端
- 2023-04-13·人工智能
- 2023-03-01·后端public class BubbleSort {public static void bubbleSort(int[] arr) {int n = arr.length;for (int i = 0; i < n - 1; i++) {for (int j = 0; j arr[j + 1]) {// 交换arr[j]和arr[j+查看全文十:优化 int flag=1; for(int i=0;i<arr.length;i++){ for (int j=0;j<arr.length-i-1;j++){ if(arr[j]>arr[j+1]){ int temp=arr[j+1];771分享
让 AI 分析我 3 年前写的代码,全是漏洞!
大家好,俺是程序员鱼皮。最近逛 GitHub 的时候,发现了一个挺有潜力的开源项目 —— DeepAudit,让 AI 帮你挖掘项目漏洞。  势头很猛啊,短短时间就涨了不少 star,看这 star 趋势图:  这是一个 AI 代码审计工具,能自动分析你的代码,找出潜在的安全漏洞和代码问题。 作者很贴心地提供了在线体验版,可以直接使用,当然也支持本地部署。下面鱼皮以作者部署的在线体验版,带大家体验一下这个项目的功能。  首先需要配置自己的大模型 API Key,国内的很多大模型都支持:  如果想直接导入并分析你在 GitHub 上的项目,而不是手动上传代码,那么还需要配置 GitHub Token,让工具能够读取到你的仓库代码。  可以在 GitHub 上生成一个新的 Token:  配置好之后,就可以创建一个要分析的项目了,仓库地址要填写正确。这里鱼皮拿自己 3 年前做的个人开源项目 —— SQL 代码和数据生成器试试水。  然后新建一个审计任务:  填写要分析的分支,还可以设置一些文件排除规则,比如排除 `node_modules`、测试文件等。  点击开始分析,AI 就屁颠儿屁颠儿干活了~  我去,竟然发现了 123 个问题?!  不过能写出 123 个问题,我感觉自己也是挺厉害的哈哈哈。  进入任务详情页面,我倒要看看都是什么问题。  先看看最严重的 —— SQL 注入漏洞。  AI 的解释还挺到位的,确实存在 SQL 注入风险。当时这么写纯属图方便,没想到被 AI 一眼看穿了。  一些小细节也被指出来了,比如硬编码、异常处理不够完善、日志记录不规范等。  不过也有一些误报,AI 把正常的代码逻辑判断成了问题。  总体来说,这个工具还有进步空间,但已经很实用了。正好我们团队需要这样的代码审计工具,还是挺期待作者能进一步完善的。  审计完成后,还可以直接导出报告:  一下子就得到了整整 70 多页的专业报告!  想想我大学的时候做课设作业,要是有 AI 帮忙生成这种报告,不得爽飞边子了? 除了项目审计,作者还提供了即时分析功能。不过这个就没什么新意了,我们团队的 [代码小抄](https://codecopy.cn/)在 24 年初就已经上线了这个功能。  我体验这个项目的时候,还有很多功能没做完,不过这也是 MVP 最小可行产品原则嘛,快速验证想法最重要。  值得一提的是,作者更新非常频繁。截止到目前,项目界面已经焕然一新了,更有极客范儿。  在审计流日志页面可以看到 AI 思考分析的过程:  还有智能仪表盘,可以直观地看到代码质量趋势:  ## 最后 这个项目的实现思路其实挺值得学习的。通过调用大模型的 API,让 AI 理解代码逻辑,然后根据常见的安全漏洞模式和最佳实践进行分析,最后生成结构化的审计报告。整个流程和 AI 代码审查是类似的,但更专注于安全漏洞的发现。  对于个人开发者来说,这类工具可以帮你在提交代码前发现潜在问题,拿来检测一下自己之前的老项目代码也是不错的;对于团队来说,可以把 AI 接入到 CI/CD 流程中,自动化代码安全检查。虽然本项目暂时还有误报,但随着 AI 能力的提升,以及作者在领域知识方面的填充,相信会越来越准确。 来试试看,你的项目代码里有多少个 Bug?  ## 更多 💻 编程学习交流:[编程导航](https://www.codefather.cn/) 📃 简历快速制作:[老鱼简历](https://www.laoyujianli.com) ✏️ 面试刷题神器:[面试鸭](https://www.mianshiya.com) 📖 AI 学习指南:[AI 知识库](https://ai.codefather.cn/)
谷歌新AI工具杀疯了?免费,但有点坑!Gemini CLI 抢先实测
刚刚谷歌发布了一个很有意思的新产品 Gemini CLI,直接把 AI 塞进了终端里。  据 [官方介绍](https://github.com/google-gemini/gemini-cli),这个工具能: - 处理大型代码库(高达 100 万的 token 上下文) - 有多模态能力:能从 PDF 或草图生成新应用 - 能自动化运维:帮你查询代码合并请求、处理复杂的代码合并 - 集成了大量工具:支持连接 MCP 服务器,支持图像、视频、音频生成 - 还有内置搜索等等 对标 Claude Code,现在还有免费使用额度、而且最好的是代码开源!  听起来就很香,也难怪 1 天的时间就 **狂涨 2w star**,火箭式涨星:  那这个工具到底是香水还是翔水呢?我来带大家体验一下。 注意,我不是专业的测评者,只是作为普通的程序员用户,说说我自己真实的使用感受。 ⭐️ 推荐观看视频版:[https://bilibili.com/video/BV1LuKdzjEAc](https://www.bilibili.com/video/BV1LuKdzjEAc/) ## 安装启动 按照官方提供的文档呢,我们要先安装 Node.js 前端运行环境,直接去 [官网](https://nodejs.org/) 安装就好了,**注意版本要 >= 18**。然后打开终端,输入一行命令全局安装工具就好: ```shell npm install -g @google/gemini-cli ``` 安装完成后,输入 `gemini` 命令,做一些基础的设置:  接下来是关键了,需要经过一波账号验证,个人用户选第一项就好。  这里大家可能会遇到 2 种验证失败的情况,第一种是网络原因(这个不好搞),第二种是说账号类型不符合要求,如图:  对于第二种情况,解决方案很简单,进入 [Google Cloud](https://console.cloud.google.com/) 控制台,新建一个项目得到 `project_id`:  然后在终端输入下列命令设置环境变量,重试就能登录进去了。 ```shell export GOOGLE_CLOUD_PROJECT=<你的 project_id> ``` 登陆成功后,我们就可以折腾了~ ## 体验一下 接下来我选了 8 个不同的场景来从多个方面验证它的能力,大家也可以感受下 Gemini CLI 的真实水平到底如何,大家说好才是真的好。  #### 1、基础问答 输入提示词: ```markdown 你好,请问你能做些什么?有什么优势? ``` 结果没想到,一上来就报错了?而且各种胡言乱语,李在赣神魔?  过了一会儿,终于满屏飘红了,看报错的意思是我没开启 API 权限:  直接访问错误信息中的网址,就能去控制台开启 API 权限了,开一下开一下:  再来!这次 AI 的回复就对味儿了,他说自己是个AI软件工程师,确保操作的透明和安全。结果还可以,就是速度有点小慢啊,这么一个简单的问题花了 20 秒,这也是智能体的一个副作用吧。  #### 2、网页搜索 输入提示词,让 AI 自动上网下载表情包: ```markdown 请帮我获取 10 张健康的熊猫头表情包,并且下载到当前目录下 ``` 结果 AI 给我推荐了几个表情包网站,无法直接下载:  是不是不支持下载工具啊? 我们输入下 `/` 键,就可以看到 Gemini CLI 支持的命令:  进入查看工具列表,发现好像没有网页资源下载工具,也是难为 AI 了。但是它支持编写 Shell 脚本,所以我们不妨引导 AI 编写脚本来实现资源下载。  提示词: ```markdown 请帮我获取 10 张健康的熊猫头表情包,并且下载到当前目录下,你可以通过编写可执行脚本来实现图片的下载 ``` 这次就可以看到智能体开始自主规划任务了,先创建了一个脚本,然后 “写文件” 操作需要我们确认,这里建议选择仅允许一次,安全一些:  遇到问题它会尝试 **重新规划** 然后重试,这也是智能体的一个关键能力:  任务执行完后还记得清理脚本,这个很不错。  好了,大功告成,我们看看下载好的文件,这个尺寸是认真的么?果然翻车了,下载的图片根本不对!  #### 3、文件操作 输入下列提示词,让 AI 帮忙处理我本地的表情包文件: ```markdown 帮我把所有的表情包尺寸放大 1 倍,并且转换为 WEBP 格式,然后将所有表情包组合在一起生成为 GIF ``` 然后应该要指定文件路径吧,不然 AI 可能不知道要处理什么。 结果当我输入 `@` 键指定文件路径时,好家伙,输入框直接卡死了?该说不说,这个交互体验不够好,我每次选择文件都会卡,而且选择不了目录。  经过一番折腾,我发现 得慢点选择,跟着程序列举出的目录树进行选择,就先选一个图片吧:  好,这次 AI 聪明了,问我是不是要处理多个文件,必须的:  然后 AI 发现无法处理图片,要下载一个图片处理工具,然后它说要利用 Mac 上的软件包管理工具来安装,同意即可:  经过漫长的等待,等了快 10 分钟竟然还没好?!  可能是我自己的网络原因吧,但我实在等不下去了。老实说测到这里,我心态都已经有点崩了,凌晨两点半隔这儿等软件安装?  不是,这玩意你写个简单的 Python 脚本不就搞定了? 感觉这个工具还是得给程序员用,要稍微加一些引导,比如我们让 AI 利用 Python 脚本实现任务: ```markdown 帮我把所有的表情包尺寸放大 1 倍,并且转换为 WEBP 格式,然后将所有表情包组合在一起生成为 GIF,使用 Python 脚本实现 ``` 可以看到 AI 安装了图像处理库,然后创建了一个虚拟环境,你别说它对安全性的考虑还是 ok 的:  然后编写脚本并执行:  任务成功完成,看下效果:  尺寸确实放大了,格式也转换成功了,GIF 也成功生成了。终于顺利完成了一次任务,还不戳。通过这种方式处理本地图片确实是要比网页端的 AI 应用方便很多。 #### 4、生成代码 输入下列提示词,让 AI 帮我做个像素摄影网站: ```markdown 请帮我制作一个网站,能够调用摄像头进行拍照,并将照片转为像素风,支持下载,要求界面简洁炫酷 ``` 这次生成速度还是挺快的,就是过程中得多次进行人工确认:  我们来看下生成的网站效果:  可以调整像素的密度,还可以一键下载照片,效果还是挺不错的,这次任务 AI 也顺利完成了~  #### 5、解释代码 给刚刚生成的项目补充一个学习指南,输入提示词: ```markdown 帮我生成该项目的学习指南,帮助新开发者快速上手 ``` 由于 AI 有上下文,它直接 get 到了我想让他分析哪个项目,然后很快生成了一个项目文档。  然后我让 AI 帮我打开文档文件:  本来是想让 AI 直接打开 Markdown 阅读软件的,但没想到它直接给我输出了一堆无关的内容,我表示不理解。  那我自己打开好了吧,生成的文档内容还是过关的,标准的 GitHub 开源项目文档。  #### 6、生成架构图 好,鉴于刚刚的任务完成得还可以,我们加大难度。让 AI 生成一个项目的分层架构图: ```markdown 帮我针对当前项目,生成分层架构图 ``` 结果就有点乌龙了,AI 给我生成了一个架构设计文档:  你管这纯英文文档叫做架构图?  那我再发挥一下仅存的专业性,让他帮我生成架构图的绘图代码: ```markdown 帮我针对当前项目,生成分层架构图的 draw.io 代码 ``` 这次看着靠谱多了:  来,我们把 AI 生成的架构图代码文件拖到 draw.io 中打开。 不是哥们?你管这叫架构图?  来,同样的任务,我们用 Cursor + Claude 4 试一试。 哎,你看人家 Claude 很有自信,说 “我可以为您生成一个更完整和详细的分层架构图”:  好,看下生成后的效果,是不是高下立判啊!  #### 7、生成可视化图表 让 AI 帮我分析项目的提交记录,输入提示词: ```markdown 根据当前项目的提交记录,生成可视化图表,便于我来分析项目的发展历程 ``` 可以看到 AI 使用 git log 命令查看代码提交记录,然后开始生成图表。  等等?图表在哪儿呢??? 我的预期肯定是生成一个图片,或者起码是一个字符画,看着像图也行啊,有点为难他了。 #### 8、多模态 等验证到多模态的时候已经是凌晨 3 点,我都已经麻了,唉,最后再坚持试试多模态吧。 输入生成图片提示词: ```markdown 帮我基于当前目录下的图片,生成一个风格相似的新图片 ``` 这次 AI 干脆直接拒绝了,不支持图片创作,你倒是写个脚本啊?!你不用 AI,用个图像处理也行对不对?  那再解释个图片试试,输入解释图片提示词: ```markdown 帮我解释当前目录下所有的图片 ``` 这倒是解释出来了,吐槽一下,竟然还是英文输出,可能跟程序本身的语言设定有关吧,体验没有那么好。  Gemini CLI 背后用的应该是 Gemini 2.5 Pro 模型,是具有原生多模态输入能力的,也就是说能识图,但是并不能创作图片,包括创作音频和视频应该都是通过第三方大模型(或者 MCP 工具实现的)。 最后再让他解释个 PDF 吧,输入提示词: ```markdown 帮我总结 PDF 的内容,并生成一个新的 PDF ``` 结果出乎我意料了,AI 提示输入超出了 token 限制?  不是号称 100 万 token 上下文么,怎么读个微型 PDF 就超出限制了呢?你无法生成 PDF 我都不觉得奇怪,我这个 PDF 文件就那几个字几张图,为什么?  本来还想让他生成音频和视频的,算了算了,我对这个工具已经有一些自己的判断了。 ## 总结 最后总结一下吧,测试了 8 个维度后,我的感受是 “一言难尽”,可能是我对 Google 预期太高了吧。 不过说实话,我确实没有发挥到 100 万 tokens 上下文的威力,测试的都是短任务,**因为在这个小黑框里去跑长任务,执行过程的浏览体验确实不够好。** 那先说说优点,**终端操作本地文件确实更方便**,而且它可以直接一行命令安装,在已有的终端中使用,不用重新下载一个终端软件,这点还是不错的。 但是问题也很明显啊,首先 AI 智能体本身的效果咱就不多说了,大家也都看到了。抛去这个之外啊,非程序员使用它的门槛还是比较高的。终端的交互体验确实是不如网页和客户端的,很难看到思考过程,界面展示和交互效果也就那样。利用 AI 来生成一下终端命令我觉得很棒(比如 Wrap AI),但如果你非要在这个框里使用 AI 来生成内容,我觉得大可不必吧,至少我应该不会这么干。 现在各家都在卷 AI,卷的是什么?**易用性、成本、效果。** 像我平时生活中会用豆包或者元宝,非常方便,有问题直接语音就能输入;专门编程做项目的时候会用 Cursor + Claude 的组合。那你说 Gemini CLI 的应用场景在哪里?我总不能平时有问题的时候,第一时间打开终端来问吧?用它生成代码也不好直接编辑呀。可能对擅长 Linux 服务器操作的技术大佬还有点用,但是在公司服务器上用这个还是要注意安全性。 所以我觉得中规中矩吧,没有到网上铺天盖地吹嘘的那种程度,现阶段这玩意更适合尝鲜和学习,而不是作为日常提效工具来使用。不过虽然现在体验一般,考虑到 Google 的技术实力、还有开源免费的发展模式,我相信随着版本迭代,这工具也会越来越好的。**而且对我们来说多一种工具的选择,总不是坏事。** 大家觉得这个工具怎么样呢?欢迎评论区留言。感兴趣的同学也可以体验一下,看看是不是和我的感受相同,还是说有一些正确的使用方式和技巧,也欢迎评论区分享。学编程和 AI 的同学,记得关注鱼皮哦,下期见~ ## 更多 💻 编程学习交流:编程导航:https://www.codefather.cn/ 📃 简历快速制作:老鱼简历:https://laoyujianli.com ✏️ 面试刷题神器:面试鸭:https://mianshiya.com 📖 AI 学习指南:AI知识库:https://ai.codefather.cn/
面向对象编程 vs 函数式编程
## 什么是面向对象与函数式编程? 编程范式主要分为面向对象编程(Object-Oriented Programming,简称OOP)和函数式编程(Functional Programming,简称FP)。理解这两种编程范式,有助于我们选择合适的方式来解决问题,提高代码的可维护性和扩展性 ### 面向对象编程 它是属于 **命令式编程** ,命令式编程还包含了过程式编程。面向对象编程是一种以对象为中心的编程范式,它将数据和操作数据的方法组织在对象中,特点如下: - 封装:将数据和方法捆绑在一个对象内部,对外只暴露必要的信息,通过访问控制保护数据的完整性 - 继承:允许类重用其他类的代码,通过不断抽象,减少重复代码 - 多态:同一个接口可以有多种实现,对象可以根据上下文采取不同的形态来提高代码的灵活性和可扩展性 ### 函数式编程 它属于声明式编程,它还包含了逻辑式编程、数学编程及响应式编程等方式。函数式编程是一种以函数为核心的编程范式,例如我们经常听到的”函数是一等公民“,它的特点如下: - 纯函数:相同输入始终产生相同输出,没有副作用,不依赖外部状态 - 不可变性:数据一旦创建就不可变,修改数据就返回新的数据 - 高阶函数:函数可以作为参数和返回值 # 面向对象编程体验 下面我们使用面向对象编程思想来做一个 Todo List 代码我放到代码小抄:https://www.codecopy.cn/post/81sxun 因为是一个 HTML 代码,大家可以直接运行调试,我们来看一下 JavaScript 代码部分: ```js // Todo 项类 class TodoItem { constructor(id, text, completed = false) { this.id = id; this.text = text; this.completed = completed; } toggle() { this.completed = !this.completed; } } // Todo 列表管理类 class TodoList { constructor() { this.todos = []; this.loadFromLocalStorage(); } addTodo(text) { const id = Date.now(); const todo = new TodoItem(id, text); this.todos.push(todo); this.saveToLocalStorage(); return todo; } removeTodo(id) { this.todos = this.todos.filter((todo) => todo.id !== id); this.saveToLocalStorage(); } toggleTodo(id) { const todo = this.todos.find((todo) => todo.id === id); if (todo) { todo.toggle(); this.saveToLocalStorage(); } } saveToLocalStorage() { localStorage.setItem("todos", JSON.stringify(this.todos)); } loadFromLocalStorage() { const stored = localStorage.getItem("todos"); if (stored) { const parsedTodos = JSON.parse(stored); this.todos = parsedTodos.map((todo) => new TodoItem(todo.id, todo.text, todo.completed)); } } } // Todo 应用类 class TodoApp { constructor() { this.todoList = new TodoList(); this.render(); } addTodo() { const input = document.getElementById("todoInput"); const text = input.value.trim(); if (text) { this.todoList.addTodo(text); input.value = ""; this.render(); } } removeTodo(id) { this.todoList.removeTodo(id); this.render(); } toggleTodo(id) { this.todoList.toggleTodo(id); this.render(); } render() { const todoListElement = document.getElementById("todoList"); todoListElement.innerHTML = ""; this.todoList.todos.forEach((todo) => { const li = document.createElement("li"); li.className = `todo-item ${todo.completed ? "completed" : ""}`; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = todo.completed; checkbox.onclick = () => this.toggleTodo(todo.id); const text = document.createElement("span"); text.textContent = todo.text; const deleteButton = document.createElement("button"); deleteButton.className = "delete-btn"; deleteButton.textContent = "删除"; deleteButton.onclick = () => this.removeTodo(todo.id); li.appendChild(checkbox); li.appendChild(text); li.appendChild(deleteButton); todoListElement.appendChild(li); }); } } // 初始化应用 const todoApp = new TodoApp(); // 添加回车键支持 document.getElementById("todoInput").addEventListener("keypress", function (e) { if (e.key === "Enter") { todoApp.addTodo(); } }); ``` 这里我们创建了三个类,分别是:TodoItem 类:表示一个待办事项,包含ID、文本和完成状态。提供切换完成状态的方法;TodoList 类:管理待办事项的列表,提供添加、删除和切换完成状态的方法。数据持久化到本地存储等方法。TodoApp 类:负责UI渲染和用户交互,包括添加、删除和切换待办事项的完成状态。我们通过抽象这个功能为三个类,每个类来实现单一的逻辑,然后外层调用里层的类,**就像洋葱一样,把复杂的逻辑封装在了里面**。使用者无需关心怎么实现的,只需要知道怎么使用就好了 # 函数式编程体验 完整代码还在这里:https://www.codecopy.cn/post/81sxun ```js // 创建新的 todo 项 const createTodo = (text) => ({ id: Date.now(), text, completed: false, }); // 切换 todo 完成状态 const toggleTodo = (todo) => ({ ...todo, completed: !todo.completed, }); // 过滤删除指定 todo const removeTodoById = (todos, id) => todos.filter((todo) => todo.id !== id); // 添加新的 todo const addTodo = (todos, text) => [...todos, createTodo(text)]; // 切换指定 todo 的状态 const toggleTodoById = (todos, id) => todos.map((todo) => (todo.id === id ? toggleTodo(todo) : todo)); // 本地存储操作 const storage = { save: (todos) => localStorage.setItem("todos", JSON.stringify(todos)), load: () => JSON.parse(localStorage.getItem("todos") || "[]"), }; // 渲染函数 const renderTodos = (todos, handlers) => { const todoList = document.getElementById("todoList"); todoList.innerHTML = ""; const createTodoElement = (todo) => { const li = document.createElement("li"); li.className = `todo-item ${todo.completed ? "completed" : ""}`; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.checked = todo.completed; checkbox.onchange = () => handlers.onToggle(todo.id); const text = document.createElement("span"); text.textContent = todo.text; const deleteButton = document.createElement("button"); deleteButton.className = "delete-btn"; deleteButton.textContent = "删除"; deleteButton.onclick = () => handlers.onDelete(todo.id); li.appendChild(checkbox); li.appendChild(text); li.appendChild(deleteButton); return li; }; todos.forEach((todo) => { todoList.appendChild(createTodoElement(todo)); }); }; // 应用状态管理 const createTodoApp = () => { let todos = storage.load(); const updateState = (newTodos) => { todos = newTodos; storage.save(todos); render(); }; const handlers = { onAdd: (text) => { if (text.trim()) { updateState(addTodo(todos, text)); } }, onToggle: (id) => { updateState(toggleTodoById(todos, id)); }, onDelete: (id) => { updateState(removeTodoById(todos, id)); }, }; const render = () => renderTodos(todos, handlers); // 初始渲染 render(); return handlers; }; // 初始化应用 const app = createTodoApp(); // 事件监听设置 document.getElementById("addButton").addEventListener("click", () => { const input = document.getElementById("todoInput"); app.onAdd(input.value); input.value = ""; }); document.getElementById("todoInput").addEventListener("keypress", (e) => { if (e.key === "Enter") { const input = document.getElementById("todoInput"); app.onAdd(input.value); input.value = ""; } }); ``` 通过上面的代码,可以看到所有的逻辑都是使用函数完成的,通过纯函数和不可变性提高了代码的可预测性和可维护性。但是相比较面向对象编程的代码可能没有那么清晰 # 面向对象 VS 函数式 通过上面的代码,可以很直观的看到他们各自的优缺点,我们再对比总结一下: - 代码组织方式上:OOP 是基于对象,把数据和对应的行为封装在一起,通过类来管理代码;FP 是围绕函数来得到新的数据,数据和行为是分开的 - 状态管理上:OOP 是对象内部进行维护,通过类的方法来更新对象状态,并且状态变化可能会导致副作用;FP是通过函数返回新的数据(状态),来强调的数据不可变性 - 代码复用上:OOP 就是通过继承来实现代码复用;FP 就是通过组合函数来实现代码复用 - 使用场景上:OOP 适合有明确的对象和对应行为的系统,需要复杂状态管理;FP 适合业务开发,并发编程,数据转换和计算 我们再来看一下前端中知名的库使用的编程范式,Vue2.x 就是基于面向对象编程,React 18之前的类组件也是基于面向对象编程,每一个组件其实都是一个对象。但是在 React 18之后,就改为了函数式编程和自定义hooks,是因为类组件中复用逻辑非常麻烦,需要借助 HOC(高阶组件),但是高阶组件会创建没有必要的组件树,会产生嵌套地狱,调试也变得困难,知道 自定义 hooks 的出现,让 React 组件复用逻辑变得非常简单,我觉得最重要的点是 类组件 并没有使用到 class 的特性,我们并没有使用到封装、继承等等特性。 技术没有银弹,哪种编程方式都有弊端,只有都熟练使用才能在遇到问题选择出合适的编码方式,写出优雅的代码 # 参考 1)为什么面向对象和函数式编程都有问题:https://github.com/AlexiaChen/YinWangBak/blob/master/%E4%B8%BA%E4%BB%80%E4%B9%88%E8%AF%B4%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%BC%96%E7%A8%8B%E5%92%8C%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E9%83%BD%E6%9C%89%E9%97%AE%E9%A2%98.md
咸鱼代写代码的都是什么人?真的能有收入么?
大家好,我是程序员鱼皮。看到一个帖子,是一位博主分享自己读研期间在咸鱼上靠帮别人代写代码接单的经历。由于内容过于真实,看完之后竟让我有些红温了。 <p align="center"><img src="https://pic.code-nav.cn/migrate_image/3e0f3c24fcc22698ef93802d71f792eb.webp" alt="" width="174px" /></p> 我估计也有很多学编程的同学想自己接单帮别人代写代码,那代写代码真的能有收入么?是亏麻了还是赚麻了? 下面我先给大家简单分享一下这位博主代写代码的经历,答案就不言而喻了。 他虽然总共只接了 5 个单,但见识到了各种 “奇怪” 的需求。 第一单是一位大学生,需求是 “使用目标检测的算法检测火星陨石坑”。好家伙,深度学习 + 天文学?听起来就很麻烦。沟通后发现没有现成的数据,而且目的竟然只是为了应付课程作业?于是果断放弃接单,不仅收入 0 元,还浪费了很多天的时间交流 ❌。 第二单是一位研究生,需求是 “实现一个评价指标的代码”。按照要求完成后,竟然被对方说不符合预期,而且可气的是还是对方模型的问题!但客户是上帝,这个时候就很难办。直接不做了?之前不就白写了。于是只能主动帮他分析实验 + 查论文。花了 3 天的时间,最后收入 298 ✅。 第三单是一位大学生,需求是 “实现一个插值函数代码”,也是为了完成课程作业。代码 10 分钟就搞定了,以为这单好搞呢,结果对方表示代码跑不起来。打字沟通很多轮都没用,最后直接远程帮他配了环境,收入 99 ✅。 第四单就更离谱了,给我看的直接高血压!这是一位研究生,需求是 “完成 sklearn 包入门学习的课程作业”。刚开始非常愉快,直接在对方的代码上补充一些就搞定了,结果对方还要帮他完成一份实验报告???这必然是不会答应的啊。结果他表示后面还有多份作业需求,所以还是帮他写了。但结果是后来他再也没联系过。。。✅ 最后一单是一位研究生,需求是 “复现某论文的实验”。接单时直接甩了一段需求文字,但明显是论文的机翻。找他拿到原论文后沟通需求,发现他连最基本的概念也搞不清楚,为了避免后续扯皮,果断放弃 ❌。 大家看了之后有什么感受?总结一下就是:自主接单时,需求不确定性太高,沟通和返工成本实在是太大了,而且客单价不高,性价比太低! 虽然看起来博主是小赚,但隔着屏幕都能感觉到心很累。所以在经历这几个订单之后,这位博主也放弃了代码代写的服务。毕竟单从收入来考虑,我的评价是 “不如洗盘子”。不过这段经历相信也让他受益匪浅吧。 因为我做知识分享很多年了,大学期间也在学校开过科创课,给很多同学做过答疑,我特别能感同身受。大多数情况下,会在淘宝、甚至是咸鱼二手平台上找别人代写代码的人,自己的编程水平都是较低的,且不说课程作业做不出来,连环境都不一定能自己配好。有一定编程项目经验的人都知道,找别人做项目的核心难点在于 **把需求描述清楚** ,而对于基础较差的同学,他如果自己能把需求描述地很清楚,那想必也不会花钱找人代写代码了。毕竟我也是从学生时代过来的,我们普遍爱好白票,这太正常了。 根据我的经验,价格越低的单,往往越难接,因为客户基础更差、要求反而更多;而价格高的单,往往。。。也不好接,因为需求复杂度会更高,而且容易出现需求的变更。 钱难赚,屎难吃。 <p align="center"> <img src="https://pic.code-nav.cn/migrate_image/7c935c2a26ed88104d2723bb4c4fda8c.webp" alt="" width="300px" /></p> 既然这样,为什么淘宝和咸鱼上还会有那么多代写代码的店铺呢?在这些平台上代写代码的都是什么人? 打开一看就知道了,先看某宝: <img src="https://pic.code-nav.cn/migrate_image/fe4463b1aa46ea2a4e8e873282522234.webp" alt="" width="100%" /> 我写的代码都没这些店铺的海报整齐!单从封面图的各种编程语言和精心设计的海报来看,就知道肯定是工作室 —— 专业团队在搞了。 而且据我了解,所谓的专业团队,有可能只是 **中间商** 喔。这部分人甚至都不是程序员,把别人下的订单再外包给一些技术群的朋友们,自己就能拿抽成。我们不生产需求,我们只是需求的搬运工;我们不做需求,我们只是甲方找的甲方。 此外,当然也不排除一些外包公司在淘宝上开店铺引流的可能。 再看下咸鱼,就没有那么整齐了: <img src="https://pic.code-nav.cn/migrate_image/5c9d3abd0278bbb82cccd244a4dfcf17.webp" alt="" width="100%" /> 说实话,我有点难受了,咸鱼上还真是啥都有啊,编程拜师都出来了。不到 10 块钱帮你写代码、不到 200 块找一个大佬拜师,你会信么?这不过是引流手段罢了,跟 1 元小课一样。但你别说,还真有人信! 从上图也能看出来,咸鱼上接单代写代码的更多的是 “个人”,靠谱程度就不好说了,一般是学生或者上班带薪摸鱼的程序员,想找一个大佬是不太现实的。想想就知道了,真正的大佬都是按时薪去衡量自身价值的,大佬会花 1 个小时的时间赚你那几十块钱的作业钱么? 如果是我的话,我反而会选个价格标的相对高点的,明码标价起码比后续交流中给个高价背刺要好一些,而且最重要的是要能正常交付。几十块钱还跟你来回 Battle 改需求,换位思考一下,能给你做好了么?搞不好再给你代码里藏点毒。 所以,找别人代写代码也是一个需要谨慎考虑的操作。我之前接触过一位学长,他就是在网上找别人代写的毕业设计,结果你猜怎么着?在答辩前,辅导员专门找我来给这位买毕设的同学去讲代码!我当时看到那个毕设程序,都有点红温了,就读取个本地文件、调个现成的库而已,但凡踏踏实实学个半年,自己做出来的都比这个好,这店家属实有点黑了。 当然,也有良心店家,我毕业之后专门去调研过,加了一位毕设店主,对方竟然给我发了一个表格,让我把毕设论文的核心思路、核心需求、导师要求什么的都写清楚,于是我果断放弃了。好家伙,我要是能把自己的需求描述地这么清楚,还需要你帮我写毕设? <img src="https://pic.code-nav.cn/migrate_image/2930f191f94d4ecc45d0be7fd7d5d451.webp" alt="" width="100%" /> 最后给一些个人建议,我劝想接单的同学先好好考虑下自己的现状和规划,一方面你看看代写代码的店铺就知道这玩意有多卷了;另外一方面接小单收益不大,有这时间精力,不如踏踏实实地多写文章积累技术,每一篇文章都是你自己的财富,而提升技术对你主业涨薪也是大有裨益的,先稳固主业,再考虑副业吧。对于学生党,可以接单,但是不要迷失方向,把心思全放在这上面,接单的目的应该是帮你提升技术成长,而不是为了赚那几斤几两。 各位小伙伴们接单或者找别人代写代码的经历么?欢迎评论区分享~ ## 更多 💻 编程学习交流:[编程导航](https://www.codefather.cn) 📃 简历快速制作:[老鱼简历](https://laoyujianli.com) ✏️ 面试刷题神器:[面试鸭](https://mianshiya.com)
快速排序-c++
#include<iostream> using namespace std; int partition(int(&)[10], int start, int end); void printArray(const int (&)[10]); void quickSort(int(&)[10], int start, int end); void swap(int(&a)[10], int i, int j); int main() { int a[10] = { 23,45,18,6,11,19,22,18,12,9 }; printArray(a); int size = sizeof(a) / sizeof(a[0]); quickSort(a, 0, size - 1); printArray(a); cin.get(); } //定义快速排序函数,递归实现 void quickSort(int(&a)[10], int start, int end) { //基准情况 if (start >= end) return; int mid = partition(a, start, end); //递归调用,分别对两部分继续排序 quickSort(a, start, mid - 1); quickSort(a, mid + 1, end); } //按照支点分区的函数 int partition(int(&a)[10], int start, int end) { //选取支点 int pivot = a[start]; //指定指向数组头尾元素的"指针" int left = start, right = end; //如果左右指针没有相遇 while (left < right) { //左指针不停右移,直到找到一个比支点大的值:又指针不停左移,直到找到一个小的值 while (a[left] <= pivot && left < right) ++left; while (a[right] >= pivot && left < right) --right; //左右互换 swap(a, left, right); } //判断指针相遇的值,跟支点值的大小关系 if (a[left] <= pivot) { //比支点值小,就直接换到数组的头位置 swap(a, start, left); return left; } else if (a[left] > pivot) { //比支点值大,就将前一个位置的元素直接换到数组的头位置 swap(a, start, left - 1); return left - 1; } } //交换数组中额两个元素 void swap(int(&a)[10], int i, int j) { int temp = a[i]; a[i] = a[j]; a[j] = temp; } //打印输出数组 void printArray(const int(&a)[10]) { for (int num : a) { cout << num << "\t"; } cout << endl; }
快速排序算法
public class QuickSort { public static void main(String[] args) { int[] arr = {5, 2, 8, 3, 1, 6, 4, 7}; quickSort(arr, 0, arr.length - 1); System.out.println("排序结果:"); for (int num : arr) { System.out.print(num + " "); } } public static void quickSort(int[] arr, int low, int high) { if (low < high) { int pivot = partition(arr, low, high); // 获取基准元素的索引 quickSort(arr, low, pivot - 1); // 对基准元素的左侧子数组进行快速排序 quickSort(arr, pivot + 1, high); // 对基准元素的右侧子数组进行快速排序 } } public static int partition(int[] arr, int low, int high) { int pivot = arr[low]; // 选择第一个元素作为基准元素 int i = low, j = high; while (i < j) { while (i < j && arr[j] >= pivot) { j--; } if (i < j) { arr[i] = arr[j]; i++; } while (i < j && arr[i] <= pivot) { i++; } if (i < j) { arr[j] = arr[i]; j--; } } arr[i] = pivot; // 将基准元素放置到最终位置 return i; // 返回基准元素的索引 } }
从授时服务器获取时间的函数[GetNetWorkTime()]
func GetNetWorkTime() (time.Time, error) { var host string flag.StringVar(&host, "e", "182.92.12.11:123", "NTP host")//这里可以更改182.92.12.11为任何授时服务器 flag.Parse() conn, err := net.Dial("udp", host) if err != nil { return time.Time{}, err } defer func(conn net.Conn) { err := conn.Close() if err != nil { } }(conn) if err := conn.SetDeadline(time.Now().Add(15 * time.Second)); err != nil { return time.Time{}, err } req := &packet{Settings: 0x1B} if err := binary.Write(conn, binary.BigEndian, req); err != nil { return time.Time{}, err } rsp := &packet{} if err := binary.Read(conn, binary.BigEndian, rsp); err != nil { return time.Time{}, err } secs := float64(rsp.TxTimeSec) - ntpEpochOffset nanos := (int64(rsp.TxTimeFrac) * 1e9) >> 32 showtime := time.Unix(int64(secs), nanos) return showtime, err }
快速排序
public class quickSort { public static void main(String[] args) { int[] nums = {5, 2, 6, 3, 8}; quick_sort02(nums, 0, nums.length - 1); for (int i : nums) { System.out.print(i + " "); } } public static void quick_sort02(int[] p, int L, int R) { if (L >= R) return; int i = L - 1; int j = R + 1; int x = p[L + R >> 1]; while (i < j) { do i++; while (p[i] < x); do j--; while (p[j] > x); if (i < j) { //int t; //t = p[j]; //p[j] = p[i]; //p[i] = t; //位运算交换数字 p[j] ^= p[i]; p[i] = p[j] ^ p[i]; p[j] = p[j] ^ p[i]; } } quick_sort02(p, L, j); quick_sort02(p, j + 1, R); } }
Go 冒泡排序代码
package main import "fmt" func main() { // 定义切片,进行引用传递 arr := []int{5, 1, 4, 2, 8} // 使用协程加快速度 go bubbleSort(arr) fmt.Println(arr) } func bubbleSort(arr []int) { var flag = true var n = len(arr) for i := 0; i < n-1; i++ { for j := 0; j < n-i-1; j++ { if arr[j] > arr[j+1] { temp := arr[j] arr[j] = arr[j+1] arr[j+1] = temp flag = false } } if flag { continue } } }
Java 冒泡排序代码
public class BubbleSort { public static void bubbleSort(int[] arr) { int n = arr.length; for (int i = 0; i < n - 1; i++) { for (int j = 0; j < n - i - 1; j++) { if (arr[j] > arr[j + 1]) { // 交换arr[j]和arr[j+1] int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } } public static void main(String[] args) { int[] arr = {5, 1, 4, 2, 8}; bubbleSort(arr); System.out.println(Arrays.toString(arr)); } }
