30岁了才有了自己的博客
写作是很痛苦
说来惭愧,干程序员这么多年,30 岁了,终于想起来要自己搭建一个博客了!其实一直羡慕别人拥有炫酷的博客,可是自己却一直没去做。作为前端程序员,通常来说,搭建好看的博客还是非常容易的,不过因为自己没啥分享欲望,加上上学时对写作的厌烦(上学时,语文不及格的主要原因就是作文)。
为什么现在想起来搭建博客呢?
我发现,我虽然不喜欢写作,却喜欢记录,学习笔记、工作总结、快捷命令汇总等等。学习新技术时,我喜欢把教程或者书本上的语言总结成自己能看懂的语言记录起来,然后看看自己的记事本,这么多年来也有 600 多篇了。
因此,我觉得我不是不喜欢写东西,而是我内心对写作的的恐惧,也担心自己水平有限,文章不够深入,误人子弟。也是为什么我一直不敢把自己写的东西发布到网上。
[质问] 那么,为啥你现在敢发了,你水平更高了?还是你写作水平提升了??
[回答] 其实都不是!回想有一天,发现我多年前发布的第一篇文章,竟然现在都还有人收藏点赞,全文不过 500字,当时 md 语法都不知道,更别谈什么排版了。
这让我意识到一个问题:即便随手写的一篇文章,只要有完整的解决方案,都有可能帮助到别人
。
我掘金的第一篇文章。
当然,除了帮助别人,分享文章还有有什么好处呢?会获得点赞和评论;甚至会遇到一些志同道合的人加好友,一起聊技术细节。当然,对于淡泊名利的你来说,这些有什么用呢?这些东西都是正反馈的来源呀!!!
正反馈
说到正反馈,这真的是一个值得深入的话题。工作中,大多数时候的任务是重复和无聊的,你的正反馈来源多数是你的上级或者同事,可是这样的正反馈是很少的。生活中也是一样。也就导致对工作失去兴趣,对技术失去兴趣,因为没有正反馈,也就没有动力继续,不想做任何事情
。所以我们需要主动去寻找一些能产生正反馈的事情,写文章、运动、学习,分享。都可以。(有段时间,我每天上班第一件事就是看看有没有新的点赞和评论。)
其实这些道理一早就知道,很多文章、前辈都有说过,可是呢,人呀,就是要自己亲身体会后才会明白!
开始写作
与其说是开始写作,不如说是开始记录,对我来说更加合适。我并不擅长写出段落清晰、用词优美的文章。所以我决定把写作当做生活中的记录,把本地的总结记录分享到线上论坛。
既然要分享到网上,也促使我要求自己发布的文章不仅是自己能看懂,尽量也能让所有人看懂。这很重要!!!
让所有人能看懂很重要,因为即便是自己写的文章,隔一段时间你回头看,会发现啥也看不懂了,根本回想不起来当时是怎么实现的。这就是因为当时写得不够清晰。
于是,我逐渐将本地的记录文章颁发到了论坛,并获得了一些收获。
当然,这点数据根本不够看。对我来说是一个开始,打破固有思维的开始!渐渐地,我从一年 5 篇,到一年 10 篇了,现在一个月一篇。发布的文章逐渐变多了... 这可并不是我强迫自己输出呀(不可能,我意志力不强),这本来就是我本地记录的频率。
这也是我的做事风格,把一些看似很难的事情变得平常化。例如记单词,地铁上就非常适合记单词,手机没网络,记完单词刚好就到公司了。
写本篇的时候,我发现又有一段时间没更新了,不是我没写,是写了很多,都还没发布,因为总觉得少点什么。
搭建博客
说了这么多,总算可以回归正题了~
我参考了 v 友们的建议想搭建自己的博客,求大佬们推荐方案 - V2EX,总结出以下常见的博客框架。
- wordpress
- hugo
- hexo
- Astro
- typecho
不过,看了一圈,都不太适合我,要么是配置麻烦、要么主题不好看。相反,并不是这些博客不好,里面的插件、主题多得很,只是没有找到我想要的那种风格(喜欢折腾0.0),下面我会详细解释。
我喜欢的风格
之前我一直很长一段时间,我很喜欢那种酷炫的博客,例如,动态背景图(雪花、3D 地图),或者页面下方还有个可以通过鼠标挑逗的二次元卡通人。再就是搞一些炫酷跳动文字?大家应该都懂,哈哈。
而现在呢,我想要的博客就是:
”简约“
看了太多混杂广告、推广、链接的博客,我更想让读者专注在文章本身上。所以除了文章相关的内容,其他东西一律去除。
”简单“
博客要关注的是内容,所以发布流程越简单越好,我只需要关注写内容就好了。
结合这两点,并简单调研了下相关实现,还是决定自己搭建一个,比较往后较长一段时间都要用它,所以还是自己用着舒服为主。
页面展示
Github: github.com/chennlang/c…
PC 端
H5 端
![]() | ![]() | ![]() |
---|
样式参考了以下两个非常喜欢的博主。
NextJs 搭建博客
其实博客的本质,无非就是一个 markdown 的渲染器,对于前端来说,实现起来不算难事。所以我决定使用 NextJs 自己搭建一个,选用 Nextjs 的主要是因为它天然对 SSG (静态生成)的友好支持。
1.配置 nextConfig
通过如下配置,我们就可以导出一个静态web项目。
const nextConfig: NextConfig = {
/* config options here */
trailingSlash: true,
output: 'export',
};
2. 文章格式范式
我们将文章放在 posts 目录下,文章格式如下:
---
title: "时隔三年,一个可视化可交互的前端依赖分析工具发布了"
date: "2025-02-01"
categories: "Life"
summary: "我一直认为它只是一个不完美的半成品,所以我只是悄悄的上架到了个人的 github 仓库,并没打算公之于众。不过就在今天,我意外的发现仓库多了一个陌生人的 Issue,真的很震惊,我没想到真的有人安装文档安装使用了。这真的让我感到意外,也许真的有人觉得有用,那么我就要做好它,也有责任做好它!"
---
### 终于有勇气发布了~
我一直认为它只是一个不完美的半成品,所以我只是悄悄的上架到了个人的 github 仓库,并没打算公之于众。不过就在今天,我意外的发现仓库多了一个陌生人的 Issue,真的很震惊,我没想到真的有人安装文档安装使用了。这真的让我感到意外,也许真的有人觉得有用,那么我就要做好它,也有责任做好它!
3. 解析文章内容-核心逻辑
import { promises as fs } from "fs";
import path from "path";
import matter from "gray-matter";
import { remark } from "remark";
import html from "remark-html";
// 获取文章内容
export async function getPost(id: string) {
const markdownWithMeta = await fs.readFile(
path.join("posts", id + ".md"),
"utf-8"
);
// frontmatter 是文章的元数据信息, content 是文章内容
const { data: frontmatter, content } = matter(markdownWithMeta);
const processedContent = await remark().use(html).process(content);
const contentHtml = processedContent.toString();
frontmatter.categories = Array.isArray(frontmatter.categories) ? frontmatter.categories : [frontmatter.categories || "Default"]
return {
id,
contentHtml,
frontmatter,
}
}
4. 展示文章页面-核心逻辑
const Post = async ({ params }: any) => {
const { frontmatter, contentHtml } = await getPost(params.id);
return <div>
<h1 className="text-2xl mb-4 font-bold">{frontmatter.title}</h1>
{/* 元数据 */}
<p className="flex flex-wrap">
{metaList.map((item) => (
<span key={item.label} className="text-gray-600 mr-4">
<label>{item.label}: </label> {item.value}
</span>
))}
</p>
{/* 内容 */}
<div className="mt-4" dangerouslySetInnerHTML={{ __html: contentHtml }} />
<div className="mt-10">
<Comments />
</div>
</div>
};
展示就很简单了 dangerouslySetInnerHTML 将内容渲染在页面上就好了
5. 添加评论功能
因为是静态页面,所以就不存在数据库和server服务,所以最佳的方式是直接使用 giscus
利用 GitHub Discussions 实现的评论系统,让访客借助 GitHub 在你的网站上留下评论和反应吧!本项目深受 utterances 的启发。
首先我们到 giscus 上配置好我们的 github 项目,并在项目中开启 GitHub Discussions 功能。
所以本质上,所有评论最终都存在该仓库的 Discussions 里,每个文章对应一个 discussion。
对应关系可以自行设置,默认是文章的 url 路径对应作为 discussion 的标题,然后该文章的评论都是在这个 discussion 下。
然后新建一个 Comments 组件(配置需要根据自己的仓库自行修改)
"use client";
import Giscus from "@giscus/react";
export default function Comments() {
return (
<Giscus
repo="xx/xxx.github.io"
repoId="xxxx"
category="xxx"
categoryId="xxxx"
mapping="pathname"
strict="0"
reactionsEnabled="1"
emitMetadata="0"
inputPosition="top"
theme="preferred_color_scheme"
lang="zh-CN"
/>
);
}
注意:
- 本地 localhost 域名可能不会自动创建 discussion,发布到线上后就能解决。
- 自动创建Discussion的触发条件是:当用户首次在该页面发表评论或表情时才会创建。若未进行任何交互,Discussion不会自动生成。
Github Page 部署
既然输出是静态页面,可以托管的地方就很多了,前面我也提到了,我并不想做除了写内容以外的任何事,Github Page 就是很不错的选择,不仅能托管文章代码,还能配合流水线自动构建,一举两得。
- 创建仓库
新建一个
[用户名].github.io
的 Github 仓库
- 新建一个 nextjs 部署构建流水线
.github/workflows/nextjs.yml
# Sample workflow for building and deploying a Next.js site to GitHub Pages
name: Deploy Next.js site to Pages
on:
push:
branches: ["main"]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"
cache: 'npm'
- name: Install pnpm
run: npm install -g pnpm
- name: Detect package manager
id: detect-package-manager
run: |
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
echo "manager=yarn" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=yarn" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/pnpm-lock.yaml" ]; then
echo "manager=pnpm" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=pnpm" >> $GITHUB_OUTPUT
exit 0
elif [ -f "${{ github.workspace }}/package.json" ]; then
echo "manager=npm" >> $GITHUB_OUTPUT
echo "command=install" >> $GITHUB_OUTPUT
echo "runner=npm" >> $GITHUB_OUTPUT
exit 0
else
echo "Unable to determine package manager"
exit 1
fi
- name: Install dependencies
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
- name: Build with Next.js
run: ${{ steps.detect-package-manager.outputs.runner }} next build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./out
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
然后当 main
分支 push
时自动触发 NextJs 项目的构建,完成 GitHub Pages 页面部署。接下来你就可以通过 [usename].github.io
访问属于你自己的博客了!
总结
最后,如果有和我一样喜欢折腾的小伙伴,可以使用使用我的项目作为模版,搭建一个属于你自己的博客。当然,如果你觉得还不错的话,别忘了留下你的 star~ GitHub - chennlang/chennlang.github.io: blog
来源:juejin.cn/post/7474923858165252105
iOS、Android、Flutter 流行框架对比(基于 OpenAI 的数据)
背景
基于 OpenAI 最新的 o1-preview
模型,整理出来的 App 分别使用 iOS、Android、Flutter 流行框架对比,方便查阅,部分数据可能不完全准确,整体的数据质量我看了一下还是挺高,有需要的自取。
内容
自动布局的框架
iOS
Prompt: 以表格的形式输出 iOS 自动布局框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
Auto Layout | Swift/Objective-C | iOS 原生的布局系统 | 动态适应不同屏幕尺寸的布局 | N/A(原生功能) | N/A |
SnapKit | Swift | 简化 Auto Layout 的 DSL | 使用链式语法创建布局约束 | SnapKit | 19k+ |
Masonry | Objective-C | 类似 SnapKit 的 Objective-C 版本 | 使用链式语法创建 Auto Layout 约束 | Masonry | 18k+ |
PureLayout | Swift/Objective-C | 提供一致的 Auto Layout 接口 | 简化约束的创建和管理 | PureLayout | 7k+ |
Cartography | Swift | 使用纯 Swift 语法构建 Auto Layout | 通过操作符重载简化约束定义 | Cartography | 7k+ |
TinyConstraints | Swift | 轻量级的 Auto Layout 语法糖 | 更简洁地创建和管理约束 | TinyConstraints | 3k+ |
Stevia | Swift | 简洁的布局框架 | 使用声明式语法定义布局 | Stevia | 3k+ |
EasyPeasy | Swift | 简化 Auto Layout 语法的库 | 使用简洁的语法定义和修改约束 | EasyPeasy | 2k+ |
Anchorage | Swift | 提供更简洁的 Auto Layout API | 利用 Swift 特性简化约束定义 | Anchorage | 1k+ |
LayoutKit | Swift | 高性能的布局框架 | 使用纯代码创建高性能的视图布局 | LayoutKit | 7k+ |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 10 月。如需获取最新信息,请访问相应的 GitHub 仓库。
Andorid
Porompt:以表格的形式输出 Andorid 自动布局框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
ConstraintLayout | Java/Kotlin | 官方提供的强大布局系统 | 创建复杂布局,支持动画和约束 | N/A(原生功能) | N/A |
FlexboxLayout | Java/Kotlin | 类似于 CSS Flexbox 的布局库 | 实现灵活的响应式布局 | FlexboxLayout | 6k+ |
MotionLayout | Java/Kotlin | 基于 ConstraintLayout 的动画布局系统 | 创建复杂的过渡和动画 | N/A(原生功能) | N/A |
AndroidAutoLayout | Java | 自动适配各种屏幕尺寸的布局库 | 根据屏幕尺寸自动调整布局参数 | AndroidAutoLayout | 10k+ |
Anko Layouts | Kotlin | 使用 Kotlin DSL 构建布局 | 简化布局代码,减少 XML 文件 | Anko | 15k+ |
FlowLayout | Java | 支持自动换行的布局 | 适用于标签、按钮等需要自动换行的场景 | FlowLayout | 9k+ |
PercentLayout | Java/Kotlin | 支持百分比布局的库(已弃用) | 根据父布局的百分比设置子视图尺寸 | PercentLayout | N/A |
Epoxy | Java/Kotlin | Airbnb 开源的用于构建复杂列表的库 | 声明式界面,支持复杂布局和模型 | Epoxy | 11k+ |
Gr0upie | Java/Kotlin | 简化 RecyclerView 布局和适配器的库 | 轻松创建复杂的列表布局 | Gr0upie | 12k+ |
DataBinding | Java/Kotlin | 官方的数据绑定库 | 将布局与数据源绑定,减少代码量 | N/A(原生功能) | N/A |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 10 月。如需获取最新信息,请访问相应的 GitHub 仓库。
Flutter
Porompt:以表格的形式输出 Flutter 自动布局框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
Flutter 内置布局系统 | Dart | Flutter 自带的布局系统,包含多种布局组件 | 提供灵活的布局,支持多种布局方式 | N/A(原生功能) | N/A |
Responsive Framework | Dart | 响应式布局框架 | 自动适配不同屏幕尺寸和分辨率 | Responsive Framework | 2k+ |
flutter_layout_grid | Dart | 类似于 CSS Grid 的布局库 | 支持基于网格的布局方式 | flutter_layout_grid | 1k+ |
flutter_staggered_grid_view | Dart | 创建不规则网格布局的库 | 支持瀑布流布局和不规则网格布局 | flutter_staggered_grid_view | 2.5k+ |
AutoSizeText | Dart | 自动调整文本大小的组件 | 根据可用空间自动调整文本大小 | AutoSizeText | 1.5k+ |
flutter_screenutil | Dart | 屏幕适配解决方案 | 支持多屏幕尺寸、像素密度的适配 | flutter_screenutil | 5k+ |
Sizer | Dart | 使响应式设计变得简单的库 | 根据屏幕尺寸调整组件和字体大小 | Sizer | 600+ |
flutter_responsive | Dart | 响应式布局助手 | 简化不同屏幕尺寸的布局适配 | flutter_responsive | 300+ |
align_positioned | Dart | 更灵活的 Positioned 小部件 | 提供百分比定位和对齐 | align_positioned | 200+ |
FlowBuilder | Dart | 用于构建响应式和可组合的布局 | 提供灵活的流式布局 | FlowBuilder | 1k+ |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 10 月。如需获取最新信息,请访问相应的 GitHub 仓库。
网络请求的框架
iOS
Prompt: 以表格的形式输出 iOS 网络请求框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
Alamofire | Swift | 强大的网络请求库,简化 HTTP 网络请求 | 支持链式调用、文件上传下载、认证处理 | Alamofire | 40k+ |
AFNetworking | Objective-C | 功能强大的网络请求库,支持多种网络请求 | 支持数据任务、上传、下载、认证处理 | AFNetworking | 33k+ |
SwiftyJSON | Swift | 简单的 JSON 解析库 | 轻松解析和处理网络请求中的 JSON 数据 | SwiftyJSON | 22k+ |
Kingfisher | Swift | 强大的图片下载和缓存库 | 支持异步图片下载和缓存,集成到网络请求 | Kingfisher | 22k+ |
PromiseKit | Swift | 处理异步操作的 Promise 库 | 简化异步网络请求、支持链式调用 | PromiseKit | 14k+ |
Moya | Swift | 基于 Alamofire 的网络请求抽象层 | 通过 API 定义和管理网络请求,插件支持 | Moya | 15k+ |
Reachability.swift | Swift | 用于监控网络连接状态的库 | 支持监控网络状态,处理连接状态的变化 | Reachability.swift | 12k+ |
URLSession | Swift | iOS 原生网络请求 API | 完全支持 HTTP 和 HTTPS 请求 | N/A (原生功能) | N/A |
Siesta | Swift | 高级网络请求框架,支持缓存和状态管理 | 支持响应式编程、数据缓存、重试机制 | Siesta | 3.5k+ |
Networking | Swift | 网络请求简化库,支持上传下载和缓存 | 文件上传下载、自动管理缓存 | Networking | 1.1k+ |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 10 月。如需获取最新信息,请访问相应的 GitHub 仓库。
Andorid
Prompt: 以表格的形式输出 Andorid 网络请求框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
Retrofit | Java/Kotlin | 类型安全的 HTTP 客户端,用于 Android 和 Java | 简化 RESTful API 的网络请求,支持异步和同步 | Retrofit | 42k+ |
OkHttp | Java/Kotlin | 高效的 HTTP 和 HTTP/2 客户端 | 支持连接池、拦截器和超时设置等高级特性 | OkHttp | 43k+ |
RxJava | Java/Kotlin | 用于异步和事件驱动编程的响应式扩展库 | 支持异步数据流处理,可与网络请求结合使用 | RxJava | 47k+ |
Ktor | Kotlin | Kotlin 的异步网络框架,支持客户端和服务器端 | 支持多种协议,异步编程,协程支持 | Ktor | 12k+ |
AsyncHttpClient | Java | 异步的 HTTP 客户端 | 支持异步网络请求、文件上传下载 | AsyncHttpClient | 10k+ |
Volley | Java | Google 提供的网络请求库 | 支持异步请求、图片加载和缓存 | 内置于 Android(无需单独的 GitHub 仓库) | N/A |
Fuel | Kotlin | 轻量级的网络请求库 | 简化 HTTP 请求,支持并发和协程 | Fuel | 4k+ |
Ion | Java | Android 上的异步网络请求和图片加载库 | 支持异步请求、图片加载、缓存 | Ion | 7k+ |
FastAndroidNetworking | Java | 基于 OkHttp 的高性能网络库 | 支持文件上传下载、JSON 解析、缓存 | FastAndroidNetworking | 5k+ |
Apollo Android | Java/Kotlin | 用于 Android 的 GraphQL 客户端 | 支持类型安全的 GraphQL 请求和响应处理 | Apollo Android | 5k+ |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 9 月。如需获取最新信息,请访问相应的 GitHub 仓库。
Flutter
Prompt: 以表格的形式输出 Flutter 网络请求框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
http | Dart | Flutter 和 Dart 的基本 HTTP 客户端 | 简单的网络请求支持,GET/POST 等方法 | http | 1.5k+ |
Dio | Dart | 功能强大的 HTTP 客户端库 | 支持拦截器、全局配置、FormData、请求取消 | Dio | 13k+ |
Chopper | Dart | RESTful API 客户端库,类似 Retrofit | 支持代码生成、拦截器、内置 JSON 解析 | Chopper | 1.6k+ |
Retrofit.dart | Dart | Retrofit 的 Dart 实现 | 自动生成 API 调用代码,支持 RxDart | retrofit.dart | 1.8k+ |
graphql_flutter | Dart | GraphQL 客户端库 | 支持查询、变更、订阅和缓存 | graphql_flutter | 3.5k+ |
Flutter Socket.IO | Dart | Socket.IO 的 Flutter 客户端实现 | 实时通信,支持 WebSocket | socket_io_client | 600+ |
Flutter Secure Storage | Dart | 安全的密钥/值存储库 | 用于存储令牌和敏感信息 | flutter_secure_storage | 1.7k+ |
Flutter Data | Dart | 数据管理和网络请求框架 | 支持关联、缓存、离线支持 | flutter_data | 700+ |
GetConnect | Dart | GetX 框架内置的网络请求库 | 简化网络请求,集成状态管理 | GetX | 9k+ |
FlutterFire | Dart | Firebase 的 Flutter 插件 | 支持实时数据库、身份验证、云功能等 | FlutterFire | 7k+ |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 10 月。如需获取最新信息,请访问相应的 GitHub 仓库。
图片加载的框架
iOS
Prompt: 以表格的形式输出 iOS 网络图片加载框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
SDWebImage | Objective-C/Swift | 高性能的异步图片加载和缓存框架 | 支持 GIF、WebP、APNG、缩略图等格式 | SDWebImage | 24k+ |
Kingfisher | Swift | 纯 Swift 实现的轻量级图片加载框架 | 支持异步下载、缓存和处理图片 | Kingfisher | 21k+ |
Nuke | Swift | 功能丰富的图片加载和缓存框架 | 支持异步加载、缓存、预加载等功能 | Nuke | 7k+ |
PINRemoteImage | Objective-C/Swift | 高性能的图片加载和缓存库 | 支持异步下载、缓存、支持 WebP 格式 | PINRemoteImage | 6k+ |
AlamofireImage | Swift | 基于 Alamofire 的图片组件库 | 支持图像过滤、缓存和下载 | AlamofireImage | 4k+ |
YYWebImage | Objective-C | 高性能的异步图片加载和缓存框架 | 支持 GIF、APNG、WebP、缩略图等格式 | YYWebImage | 4k+ |
HanekeSwift | Swift | 轻量级的缓存库,支持图片缓存 | 支持内存和磁盘缓存,自动缩放图片 | HanekeSwift | 5k+ |
SDWebImageSVGKit Plugin | Objective-C/Swift | SDWebImage 的 SVG 支持插件 | 支持加载和显示 SVG 格式图片 | SDWebImageSVGCoder | 300+ |
UIImageView+AFNetworking | Objective-C | AFNetworking 的 UIImageView 类别 | 为 UIImageView 提供异步图片加载 | UIImageView+AFNetworking | 33k+ (AFNetworking) |
ImageLoaderSwift | Swift | 简单的图片加载和缓存库 | 支持异步加载、缓存和占位图 | ImageLoaderSwift | 1k+ |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 10 月。如需获取最新信息,请访问相应的 GitHub 仓库。
Andorid
Prompt: 以表格的形式输出 Andorid 网络图片加载框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
Glide | Java/Kotlin | 高效的图片加载和缓存库 | 支持异步加载、内存和磁盘缓存、GIF 支持 | Glide | 34k+ |
Picasso | Java | 简单强大的图片下载和缓存库 | 处理图片加载、缓存,支持转换和裁剪 | Picasso | 19k+ |
Fresco | Java | Facebook 开源的图片库 | 支持大图加载、GIF、WebP、渐进式加载 | Fresco | 18k+ |
Coil | Kotlin | 基于 Kotlin 的现代图片加载库 | 支持异步加载、内存和磁盘缓存、动画 | Coil | 9k+ |
Universal Image Loader | Java | 过时但曾经流行的图片加载库 | 支持异步加载和缓存 | Universal Image Loader | 16k+ |
Glide Transformations | Java | Glide 的图像转换工具集合 | 提供多种图像转换效果 | Glide Transformations | 10k+ |
Picasso Transformations | Java | Picasso 的图像转换工具集合 | 提供多种图像转换效果 | Picasso Transformations | 5k+ |
AndroidSVG | Java | 支持加载 SVG 矢量图的库 | 解析和渲染 SVG 文件 | AndroidSVG | 2k+ |
Subsampling Scale Image View | Java | 支持显示超大图片的控件 | 支持平滑缩放和拖动大尺寸图像 | Subsampling Scale Image View | 8k+ |
ImageLoader | Java | 轻量级的图片加载库 | 支持异步加载和缓存 | ImageLoader | 16k+ |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 10 月。如需获取最新信息,请访问相应的 GitHub 仓库。
Flutter
Prompt: 以表格的形式输出 Flutter 网络图片加载框架,包括使用的编程语言、描述、功能、GitHub 仓库地址和 Star 数量
框架/库名称 | 编程语言 | 描述 | 功能 | GitHub 仓库地址 | Star 数量 |
---|---|---|---|---|---|
cached_network_image | Dart | Flutter 的缓存网络图片加载库 | 支持图片缓存、占位符、错误图片显示等 | cached_network_image | 5k+ |
flutter_svg | Dart | SVG 矢量图支持库 | 加载和渲染 SVG 格式的矢量图 | flutter_svg | 3k+ |
photo_view | Dart | 图片查看组件 | 支持图片缩放、平移,适用于图片预览 | photo_view | 1.6k+ |
extended_image | Dart | 功能丰富的图片加载和处理库 | 支持缓存、编辑、裁剪、旋转等高级功能 | extended_image | 1.5k+ |
flutter_advanced_networkimage | Dart | 高级网络图片加载库 | 支持缓存、加载进度指示、重试机制等 | flutter_advanced_networkimage | 800+ |
octo_image | Dart | 提供多种占位符和淡入效果的图片加载库 | 支持占位符、淡入淡出动画、错误重试 | octo_image | 500+ |
flutter_blurhash | Dart | 显示模糊占位符的库 | 支持使用 BlurHash 占位符,提高加载体验 | flutter_blurhash | 500+ |
progressive_image | Dart | 渐进式图片加载库 | 支持先加载低分辨率图像,后加载高清图像 | progressive_image | 200+ |
flutter_luban | Dart | 图片压缩库 | 实现了鲁班算法的图片压缩 | flutter_luban | 500+ |
flutter_advanced_networkimage | Dart | 高级网络图片加载库 | 支持缓存、加载进度指示、重试机制等 | flutter_advanced_networkimage | 800+ |
请注意,Star 数量可能会随着时间变化,以上数据截至 2023 年 9 月。如需获取最新信息,请访问相应的 GitHub 仓库。
数据洞察
针对文档内容,我将分别为 iOS、Android 和 Flutter 提供一些有价值的观点,基于框架的特性、开发趋势及其在各自生态系统中的重要性。
iOS 平台的分析
- 自动布局系统(Auto Layout 与替代方案) :
- Auto Layout 是 iOS 的原生布局系统,它的动态布局功能使得 iOS 应用能够很好地适配各种设备的屏幕尺寸。随着新设备的推出,开发者需要确保应用的界面能在不同屏幕上保持一致,Auto Layout 提供了很好的适配解决方案。
- 但是,由于 Auto Layout 语法相对繁琐,开发者更倾向于使用第三方库来简化布局创建,例如 SnapKit(19k+ stars)和 Masonry(18k+ stars)。这些库通过链式语法让布局的书写更具可读性,提升了开发效率。
- 网络请求的主流选择:
- Alamofire(40k+ stars)是 iOS 开发中最受欢迎的网络请求框架,它简化了 HTTP 请求,支持文件上传、下载及认证处理。它的链式语法大大提高了网络请求的简洁性和可读性,因此广泛应用于项目中。
- 对于 Objective-C 开发者,AFNetworking(33k+ stars)依然是稳固的选择,尽管 Swift 逐渐成为主流编程语言,AFNetworking 仍在老项目维护和过渡期间使用。
- 图片加载和缓存技术:
- SDWebImage(24k+ stars)和 Kingfisher(21k+ stars)是 iOS 中最常用的异步图片加载和缓存库。两者支持图片的异步下载、缓存和格式处理,尤其适合优化应用的性能并提升用户体验。
Android 平台的分析
- 布局系统的灵活性:
- Android 的官方布局工具 ConstraintLayout 是目前最强大的布局系统,支持复杂的布局和动画。它结合了 MotionLayout 等工具,让开发者能够轻松实现 UI 动效,这一点在高交互性应用中至关重要。
- 另一个重要的布局库是 FlexboxLayout,它借鉴了前端开发中 CSS 的布局方式,使得 Android 开发者可以使用响应式设计来适配多种屏幕尺寸和分辨率。
- 网络请求的多样化选择:
- Retrofit(42k+ stars)和 OkHttp(43k+ stars)是 Android 社区中最受欢迎的网络请求库。Retrofit 简化了 API 定义和请求处理,尤其适合 RESTful API 的集成;而 OkHttp 则因其高效的 HTTP/HTTP2 支持和先进的特性如连接池、超时机制等,成为高性能应用的首选。
- 图片加载技术:
- Glide(34k+ stars)和 Picasso(19k+ stars)是 Android 开发者的常用工具。Glide 提供了更强大的缓存管理和内存优化能力,适合对性能要求较高的应用,而 Picasso 则以简单的 API 接口著称。
Flutter 平台的分析
- 响应式布局的优势:
- Flutter 的内置布局系统提供了丰富的布局组件,可以轻松实现不同屏幕的适配。结合 Responsive Framework(2k+ stars)等框架,开发者能够快速构建适应各种屏幕尺寸的应用,确保跨平台应用的 UI 一致性。
- Dart 语言和网络请求框架:
- Dio(13k+ stars)是 Flutter 社区的主流 HTTP 客户端库,支持全局配置、拦截器、请求取消等高级特性,非常适合复杂网络请求场景。其功能与 Android 的 Retrofit 类似,但通过 Dart 实现,与 Flutter 的生态系统紧密结合。
- 图片加载的灵活性:
- cached_network_image(5k+ stars)是 Flutter 中最受欢迎的图片加载库,支持缓存和占位符功能,这在跨平台应用中有助于提升用户体验,尤其是在网络环境不稳定的情况下。
总结
- iOS 开发者在布局时普遍依赖 Auto Layout,但为了简化布局代码,越来越多开发者选择 SnapKit 或 Masonry 等第三方库。网络请求和图片加载方面,Alamofire 和 SDWebImage 占据主导地位。
- Android 开发在布局上具有更多选择,尤其是 ConstraintLayout 和 FlexboxLayout,这两者提供了极大的灵活性。网络请求方面,Retrofit 和 OkHttp 继续主导市场,而 Glide 则成为处理图片的首选库。
- Flutter 由于其跨平台特性,布局和网络请求框架都高度依赖于 Dart。Dio 和 cached_network_image 是最受开发者青睐的选择,特别是在需要处理多屏幕适配和网络资源加载的场景中。
通过这些框架的对比,可以更好地理解不同平台开发的趋势和工具的选择,为优化项目开发流程提供有力支持。
来源:juejin.cn/post/7430770689684865078
我在成都教人用Flutter写TDD(上)——为啥要搞TDD?
哈喽,我是老刘
写这篇文章的时候刚回到北京,之前的一周去成都帮助一家公司完成基于Flutter的TDD流程的搭建。
这个工作一半是敏捷教练,一半是Flutter相关的技术顾问。
起因是这个客户接了一份欧洲那边的开发项目,但是欧洲客户对项目流程的要求比较高,要求开发团队采用TDD流程。
这是老刘第二次碰到要求开发采用TDD的情况,而且都是欧美客户。
为啥欧美软件开发团队对TDD这样的敏捷开发趋之若鹜,而大多数国内团队却鲜少能真正搞起来敏捷开发?
是我们比欧美开发者更务实还是敏捷开发确有其独到之处?
老刘希望用这篇文章尝试解答一下。
我记得刚去成都的时候大家一起吃饭,讨论起啥样的项目适合TDD,TDD会不会浪费额外的时间这些话题。
当时吃饭只是简单的聊了聊,但是让我意识到一个问题,TDD或者说敏捷开发在国内之所以一直没有真正成为主流更多的可能是意识问题。
看不到明确的收益,却能看到明确的额外工作量,怪不得众多管理者不愿意尝试。
这篇文章会回顾一下我的敏捷开发相关的经历,并借此尝试回答TDD究竟能给我们的开发带来什么收益。
初试敏捷:网络安全领域的尝试
老刘我带着团队使用Flutter进行App开发已经6年多了,而搞TDD的时间更长一些。
大约10多年前我还没有做客户端开发的时候,那时候我是做网络安全方面开发的,使用的是C语言。
那时候公司希望在敏捷开发方面做一些尝试,因为我在研究生期间做过一些敏捷方面的工作,所以就承担起来这个任务的研发部分。
当时公司希望尝试的是Scrum的完整流程,但是因为涉及到的团队和部门有点多,而很多团队对这个流程是持保留意见的,所以最终就没有搞成。
但是研发内部的单元测试和TDD流程因为只涉及开发团队,最终得以保留,这成为了我们探索敏捷开发的重要起点。
TDD的实践与收获
虽然最终Scrum没有成功搞起来,谈不上升职加薪 ┭┮﹏┭┮
不过在TDD的探索方面确实有很多的收获:
1、实战经验的积累
这是我第一次在百万级代码量以及几十人的项目中实施TDD流程。
相当于把以前只能算是纸上谈兵的认知变成了真正的实战经验。
2、踩坑与成长
在整个的实践过程中,我们几乎踩遍了所有可能的“坑”。
要知道防火墙项目的规模和复杂度相对于一般客户端或者服务端项目要高很多,特别是还有很多底层数据的操作和对系统内核的修改定制。
例如,我们的代码需要在MIPS架构的CPU上运行,而MIPS与常见的x86架构在字节序上存在差异。这种差异导致我们在PC端进行单元测试时,经常遇到与设备上运行时不同的错误。
这件事直接导致了后来我们对整个代码进行重构,把我们的业务逻辑和cpu架构解耦。
这是我第一次如此直观且深刻的看到架构到底是什么,也让我在后续的开发过程中开始有意识的注意架构设计的合理性。
3、TDD与架构合理性
在解决TDD实践中遇到的各种问题时,我们逐渐意识到,超过一半的问题实际上源于不合理的架构设计。
以之前提到的CPU字节序问题为例,一个合理的架构设计应该将底层数据操作与上层业务逻辑解耦。
理论上,我们都明白低耦合、高内聚的重要性,但在实际操作中,很难把握到何种程度才算真正实现。
而单元测试在这里就扮演了一个标尺的角色。
它通过测试的难易程度和可行性,帮助我们检验架构设计的合理性。
如果某个部分难以测试或者无法测试,这往往意味着架构存在问题。
通过这种方式,TDD不仅促进了代码质量的提升,也推动了架构设计的不断优化。
对TDD的推行加深了我们对架构合理性的理解,也让我们在后续的项目中能够更加注重架构的合理设计。
4、TDD是思维模式的转变
自己用TDD方式写代码和组织大家一起写又是一种不同的经验。
TDD本质上不是一个编码流程,而是一个设计流程。
在工作中去观察那些真正的资深程序员和新手的差别就会发现,资深程序员是在做脑力劳动,新手是在做体力劳动。
资深程序员通常是把一个功能点的逻辑链条想清楚才开始动手写,而新手却往往急于动手,发现问题后才不得不对代码做大幅的修改。
所以他们不是在加班写代码就是在加班改bug,然后就把工时卷上去了。
TDD为普通程序员提供了一条追赶资深程序员的捷径。
通过先编写单元测试,再通过测试驱动来编写正式代码,TDD迫使开发者在编码之前深入思考功能的逻辑链条。
这种拆解测试例的过程实际上是在进行设计工作,它要求开发者在动手之前就想清楚功能点的代码设计。
即使最初的设计或思考存在问题,TDD的重构步骤也会促使开发者及时发现并解决问题,减少定位问题的成本。
而且,所有的重构和修改都是在测试代码的保护下进行的,这样可以确保修改不会对现有代码造成意外的影响。
因此,TDD不仅仅是工作顺序的改变,它是一种完全不同的思维模型。
这种思维模式的转变对于提升整个团队的开发效率和代码质量都具有深远的影响。
(关于这一点我会在后面的经历中更详细的说明。)
TDD在客户端开发中的尝试
后来我开始转做Android客户端方向的开发工作。
我尝试着在客户端开发中使用TDD流程。
刚开始我以为这会是一件非常简单的事情,因为Android开发是基于Java体系的,而Java生态中有大量单元测试框架可供挑选。
万万没想到的是在Android工程中执行单元测试每次都需要等待漫长的编译过程。
基本上可以理解为执行一个单元测试就需要把整个工程编译一遍。
虽然可以通过Robolectric等库脱离对Android环境和SDK的依赖,但实际效果仍然很不理想。
要知道TDD的核心在于小步快跑,如果每个测试例都需要编译几分钟然后运行,就完全背离了TDD的出发点了。
所以这次在客户端开发中对TDD的尝试以失败告终。
Flutter带来全新的可能
大约六年前,我带领着Android开发团队,我们当时面临着一个挑战:如何解决Android和iOS两个客户端在用户体验上的差异问题,同时还需要摆脱原生项目中历史代码的泥潭。
在寻找跨平台开发框架的过程中,Flutter以其技术优势脱颖而出,但其年轻和缺乏有说服力的案例让我犹豫不决。
最终是Flutter对单元测试的良好支持让我最终下定了决心。
在此之前,我已经放弃了在手机端开发中实施TDD的想法,因为原生单元测试的体验实在令人沮丧。
但当我尝试了Flutter的单元测试后,我意识到TDD在移动开发中还能再抢救一下。
Flutter的单元测试体验可以用“舒适”来形容。
测试的运行速度达到了秒级,并且在测试场景下对UI组件的支持也非常出色。
这使得TDD从看似不可能的任务变成了一个顺畅且自然的过程。
Flutter的设计考虑到了TDD的场景,并且不仅仅是在小规模项目中,即使在大规模工程中也能实施TDD,这为移动应用开发带来了全新的可能性。
但是TDD在Flutter上的实施过程也不是一帆风顺的,主要挑战来源于两个方面:
首先,团队成员对Flutter的掌握程度不足。
由于团队中的小伙伴们都是Flutter的初学者,他们在对Flutter本身还不够熟悉的情况下尝试执行TDD,遇到了不少技术上的挑战。
其次,长期从事客户端开发的资深程序员在切换到新的开发流程时,似乎比新手更加困难。
这些资深程序员由于多年养成的开发习惯,很难立即适应TDD的模式。
他们习惯于传统的开发流程,对于TDD这种先编写测试用例再编写代码的方式感到不适应。
这种习惯的转变需要时间和实践,才能逐渐适应并掌握TDD的精髓。
面对这些挑战,我们采取了多种措施来促进TDD的实施。例如,组织定期的培训和研讨会,帮助团队成员加深对Flutter的理解,并通过实际案例来演示TDD的实践方法。同时,我们也鼓励资深程序员和新手之间的交流和合作,通过分享经验和教训,共同克服实施TDD过程中的困难。
基于Flutter的TDD带来的改变
经过半年多的学习、尝试以及代码架构的调整,我们从项目数据上看到了一些明显的变化。
首先解释一下之所以用了超过半年的时间,主要是因为我们的项目采用Flutter + 原生的混合开发模式。
刚开始的时候主要以原生代码为主,Flutter用于开发少数不重要的页面进行测试。
验证了Flutter的用户体验和稳定性后,Flutter页面的比例才开始逐步上升,然后Flutter才变为日常开发的主导。
随着Flutter变成日常开发的主要选择,可以看到几个明显的变化。
1、开发效率提升60%
这其中一半是Flutter本身优秀跨平台能力带来的。
只要不涉及原生功能,Flutter基本可以完全覆盖所有需要编写的代码。
从性能和用户体验来说Flutter页面也完全能胜任代替原生。
另外一半效率的提升则来源于TDD。
这主要体现在下面的几点。
2、提交测试后bug减少70%
这个数据其实从不同的维度统计会有不同的结果,我个人倾向于减少的bug比例会更高一些。
举个例子,产品需求写的可能是从家里坐车到全聚德烤鸭店。
开发人员要实现的是走3米到家门口——开门——走5米到电梯——按电梯哪个按钮——出电梯左转——走5米后右转出单元门……
这还只是正常情况,没有考虑如果电梯坏了怎么处理,路上有人放了东西怎么绕开。
开发工作的本质其实就是把所有正常的、异常的可能情况都进行处理。
这个过程中主要会出两种问题:
- 有些场景没有考虑到,比如没想到路上被堆了箱子需要绕路。
- 处理流程不达预期,比如考虑了绕路,但是绕路的流程不对,走不到单元门。
我们正常的瀑布流程其实就是开发人员先思考一下整个流程和都有哪些可能情况,这一步是设计阶段,更精细一些的可能还会区分概要设计和详细设计。
然后就是用代码实现这个流程,并且补充每一个细节,这一步就是编码阶段了。
开发完成后研发人员会进行简单的测试,比如验证按照自己的代码能不能走到全聚德。
如果能走到就会认为功能正常把软件交给测试人员进行更详细的测试。
测试人员会测试所有能想到的可能场景,发现某些场景走不到就给开发人员提bug。
按照这个流程写代码,出现bug是再正常不过的事情了。
主要有几个原因:
- 现实情况纷繁复杂,总会有一开始没有预料到的情况发生。甚至有些情况测试同学也没有预料到,只有APP上线了用户使用中发现了才会反馈回来。
- 即使有很严格的概要设计、详细设计流程,其精细程度也远远做不到真实代码的精细度。而设计过程越粗放,编码过程中遗漏、出错的概率就越高。
- 研发自己测试代码功能相对来说覆盖范围比较小,有些功能自己感觉实现的没问题,但是又没有测试到,只能在测试阶段由测试人员发现。
上面几种情况中第一种其实是无法避免的,而后面两种TDD都可以帮助开发人员最大幅度的降低发生的概率。
前面说了TDD是一个设计流程,它本质上代替的是概要设计和详细设计。
我们通过把一个功能需求拆分成不同的任务,把一个任务拆分成多个很具体的测试例来进行代码功能的拆分设计。
这种设计精细到什么程度呢?
每一行功能逻辑的代码都有对应的测试例,因为每一行功能代码都是测试例驱动下编写的。
而且TDD从流程上要求先写测试代码,这就强制开发者必须先进行设计层面的思考,然后才能开始编码。
这进一步避免了瀑布流程中省略或者敷衍设计流程,直接进行编码的情况。
接下来说开发者自测覆盖范围有限的问题。
基于TDD编写的代码每一行都是先有测试代码的,可以说每一行功能代码都是测试覆盖的。
可以在大概率上保证功能代码的运行结果和我们预期是一致的。
那种我写了很多代码,一运行结果和我想的完全不一样的情况在TDD中很难出现。
所以最终效果就是使用TDD流程后,我们团队提交测试的项目,总的bug数量大幅降低,测试周期和产品发布周期的稳定也得以保证。
3、交付健壮、可修改的代码
不知道各位看到这里的同学有多少是正在996的。
加班这种事在国内的软件开发领域很常见,这里面当然有即使不忙也要把工时耗够这种无效内卷的加班文化的原因。
但是也确实有很大一部分比例是因为项目进度不达预期导致开发人员不得不加班加点赶进度。
为什么项目总是面临延期的风险呢?
排除了那些管理混乱总是临时插入新需求的团队,瀑布流程本身对项目进度掌控力不足才是罪魁祸首。
做过几年开发的同学可能都碰到过这种情况:
项目刚开始的时候时间并不紧张,开发进度推进到中期发现原本以为很简单的一个对现有代码模块的修改远比想象中复杂。
要么是那坨代码和众多地方耦合,牵一发动全身,根本没法简单修改。
要么是那坨代码根本看不懂,写它的大神早已离职或者被当作大动脉裁掉了。
你薅秃仅剩的头发,熬夜加班把这部分代码改完,然后发现不是功能不好用就是原先工作正常的模块出问题了。
然后原本正常上下班的项目迭代周期就变成了996。
这种情况反复的发生,于是开发者在预估项目时间时就会增加很多的冗余时间来应对。
项目管理者知道你会预留冗余的时间,要么压缩项目进度,要么时不时的插入临时功能。
最终无效内卷的死循环达成了。
那么TDD能解决这个问题吗?
答案是可以,而且TDD能将这种情况发生的概率降到最低。
其实我们看前面说的场景,本质上就是两个问题,对现有代码“看不懂、改不动”。
先说“看不懂”的问题
TDD中业务代码都是由测试代码驱动生产的。
所以测试例和测试代码本身就是对业务代码最好的说明。
而且这种说明是站在业务代码的使用者角度,会向你展示业务代码是如何被使用的以及各种不同情况下预期的结果是什么。
另一方面,要想实现所有业务代码都基于测试代码产生,测试例的顺序必须是层层递进的。
所以基于测试例的前后顺序也很容易把握业务逻辑的脉络。
再来说“改不动”
当我们面对一团乱麻的代码,即使这份代码就是自己写的,想在上面做些修改也绝非易事。
究其根本还是当初写代码的时候只顾着实现功能,没有腾出手来对代码的合理性做一些优化。
这其实和一个程序员的能力水平关系不大。
我们的大脑本质上是单核cpu,无法同时干两件同类的事情。
比如你没办法一边和人说话一边写邮件。
同样的道理,你也没办法一边思考业务逻辑如何实现一边思考代码结构如何优化。
敏捷开发的先行者们对这种情况有很清晰的认知,所以敏捷开发中队这种情况也有不少很好的应对手段。
而TDD就是其中很有效的一种。
TDD的小步快跑最后一步就是重构。
它不要求你实现业务逻辑时就把代码结构一并调整完善,而是把重构这个动作放在了实现业务逻辑之后。
这样就可以保证每个步骤只专心的完成一件事。
写测试代码的时候就专心思考业务代码应该提供什么样的功能。
写业务代码的时候就专心思考具体的技术细节如何实现。
重构的时候就专心思考代码结构怎么样更合理更健壮。
把重构作为一个独立步骤的第二个好处是会强制你进行重构的思考。
其实大部分程序员都是有意愿去优化代码结构的。
但是如果碰到项目进度紧急或者代码结构看起来很简单清晰等情况,人的潜意识就会让我们跳过重构这个动作。
这是人脑降低功耗的一种本能行为。
TDD的好处是把这个动作流程化、标准化。走到这一步,不管代码看起来是不是很简单,你都会去想一想有没有可以重构的地方。
即使最后真的没啥可以改动的,你也尽最大可能做到了代码优化。
而且从实际经验来看,很多时候即使看起来很简单的代码,当你专门去思考重构的时候还是会有一些值得修改的地方。
TDD给重构带来的第三个好处是你的重构是在测试代码保护下进行的。
实际工作中开发人员有时候排斥重构一个重要的原因是责任问题。
比如原先代码虽然混乱,但是能正常工作,你重构了结果出现bug,在很多团队里你就要背这个锅。
TDD的特点是大部分代码都是在测试例的覆盖下。
你的重构是否会对现有代码逻辑造成影响跑一遍测试就知道了。
所以我们重构的时候也不用担心把原先好用的功能搞坏了。
同时这种所有代码都在测试保护下的特性,也解决了“改不动”的另一个原因:代码副作用。
这里说的代码副作用是指当我们为了开发新功能对原有代码进行修改时,影响原有功能不能正常运转。
这一点有过团队开发经验的同学应该深有体会。
新功能提交测试了,收到一堆bug都是影响了原先正常的功能。
当然这本质上还是架构不合理、代码混乱的原因。
但是在TDD的场景下,你要修改的代码都在测试代码的保护下,即使架构不合理也不用担心把原先的逻辑改坏了。
4、Flutter与原生代码对比
我们团队使用Flutter一年多之后,已经有几十个页面由原生迁移到Flutter上。
因为在Flutter端采用了TDD流程,对比这些页面的原生代码和Flutter代码,可以明显看出两者的不同。
Flutter代码更为整洁、清晰。
在Flutter代码中几乎找不到大段的重复代码,基本都在重构阶段被消灭了。
而Flutter代码中除了UI布局和底层的三方库封装部分,其他代码都对应的测试覆盖。
直接体现在开发过程中就是当我们同时有Flutter和原生的新需求时,Flutter投入一个人,原生两端各投入一个人,都是1周开发时间。
Flutter提测后bug数量比原生少了70%以上,三天就测试完成可以交付。
而原生端前前后后改了一周bug才勉强达到交付标准。
那段时间原生iOS端的开发同学每天晚上加班改bug到10点多(那个模块iOS端的历史代码太混乱,经常一不小心就把原先的代码改出问题)。
所以不同流程下开发效率的差距可见一斑。
而且这种差距会随着项目迭代,代码量的累积和越来越大。
总结
六年后再来看,我们的App大多数页面都已经切换到Flutter版本。
前段时间领导找我谈心好几次,都是因为我们客户端团队平均工时太少。
可是我们对比另一个产品没有使用Flutter的客户端团队,规模差不多的项目,我们的人手是他们的一半。我们交付的稳定性、项目周期可控性、bug率等指标都远好于他们。
应该说TDD充分证明了在这种长期迭代的项目中的可靠性和项目收益。
那么回到文章最开始的问题,在短期项目中是否值得使用TDD呢?
我的观点是只要你的项目不是那种写完代码不需要改bug就交付的类型,那TDD一定能带来正收益。
这几年我们也做了几个临时性的小App,都是不需要后续长期维护的那种。
我们全部沿用TDD流程,结果也如预料,项目的测试周期、交付质量和我们的主项目基本一致,远高于以前的原生项目。
所以真的不要再觉得写测试代码是花了额外的时间,这些时间本来就是用于进行设计的。
你只不过是换了一种概要设计和详细设计的方式。
而得到的收益就是你的项目从架构到代码细节的质量再到后期修改和维护的能力都得到了不可思议的提升。
好了,本文主要是结合我过往的TDD实战的经历,希望能从理论上说明TDD在不同类型的项目中能给我们带来哪些收益。
下一篇文章,我会结合成都客户的具体情况和我们自己在Flutter上实践TDD的过程来讲讲基于Flutter的TDD具体流程和技术细节。
如果看到这里的同学有学习Flutter或者TDD的兴趣,欢迎联系老刘,我们互相学习。
点击免费领老刘整理的《Flutter开发手册》,覆盖90%应用开发场景。
可以作为Flutter学习的知识地图。
覆盖90%开发场景的《Flutter开发手册》
来源:juejin.cn/post/7441479625971318835
她说:JSON 没错,但就是 parse 不过?我懂了!
技术纯享版:《不规范 JSON 怎么办?三种修复思路+代码实现》
开篇:夜色渐浓,佳人亦在
那天晚上,办公室的灯已经灭了大半,只剩几个工位发出轻轻的蓝光。中央空调早就熄了,但显示器的热度依然在屏幕前形成一圈圈淡淡的光晕。
我坐在靠窗的位置,刚把代码提交推送完,正打算收键盘走人。
这时,小语走过来,端着还冒着热气的速溶咖啡——她果然又是那个留下来最晚的人之一。
“诶~”她蹲在我旁边的桌子边上,语气带着一丝挫败,“你这边有没有遇到 JSON 字符串明明格式看着没错,却死活 JSON.parse
不过的情况?”
一个普通的错误,却不是普通的崩溃
原来她在调试一个用户日志上传模块,前端接收到的日志数据是从后端来的 JSON 字符串。
问题出在一个看似再平常不过的解析操作上——
const logData = JSON.parse(incomingString);
可是控制台总是报错:Unexpected token
。数据一眼看去也没问题,{'name': 'Tom', 'age': 30}
—— 结构清晰,属性齐全,但偏偏就是“坏掉了”。
她抿了一口咖啡,苦笑,“我知道是引号的问题,可这种数据是从破旧的系统里吐出来的,量还特别大,我不可能一个个手动改。”
风起 · JSON.parse 不是万灵药
我们一起回顾了她的实现方式。她用的是最基础的 JSON.parse()
,这是我们在项目里默认的处理方式——简单、直接、快速。
但这个方法对 JSON 格式的要求极其严格:
- 只能使用双引号
"
- 属性名必须加引号
- 不容忍任何额外字符或注释
一旦出现诸如单引号、缺少逗号、多余空格这些“微小过失”,就直接抛错了。
小语叹气,“很多时候这些 JSON 是设备端拼出来的,不规范,又没有错误提示,我根本不知道该怎么修。”
我翻了翻之前的代码,从夹缝中找出来一张破旧的黄皮纸,我们俩一起瞅了上去,看到上面写着
function tryParseJSON(jsonString) {
try {
return JSON.parse(jsonString);
} catch (e) {
// 尝试简单修复:去除可能的多余字符
const cleaned = jsonString.replace(/[^\x20-\x7E]/g, '').trim();
try {
return JSON.parse(cleaned);
} catch (e2) {
console.error("无法解析JSON:", e2);
return null;
}
}
}
下面备注了一行小字:此法在一些更轻量的场景里,做一些“简陋修复“,对于简单的问题有时能奏效,但对于更复杂的错误,比如混合了单引号和双引号的情况,只能再实现另一个方法可以做更针对性的修复方法:
function fixQuotes(jsonString) {
// 将单引号替换为双引号(简单情况)
return jsonString.replace(/'/g, '"');
}
小语感叹一声:“没有更好的了吗?”
解决篇 · 来自大佬的一句话
恰好这时,阿杰从会议室出来,耳机还挂在脖子上。
他听了一耳朵后随口说了句:“你们试过 jsonrepair
吗?那玩意能把坏 JSON 修回来,就像修车。”
“json... repair?”小语一脸困惑。
我忽然想起,之前有个日志监控服务也碰到类似的问题,当时就是用了这个库一把梭。
我打开编辑器,快速翻出来了这一段:
npm install jsonrepair
const { jsonrepair } = require('jsonrepair');
const damaged = "{name: 'John', age: 30}";
const fixed = jsonrepair(damaged); // => {"name":"John","age":30}
const obj = JSON.parse(fixed);
小语凑过来看了一眼,眼睛一亮:“它真的把引号补好了?”
我点头。这个工具是为了解决类似“非标准 JSON”问题的,它会尽可能地补全缺失引号、逗号,甚至处理 Unicode 异常字符。
当然,也不是所有情况都适用。
比如碰到乱码或者非法嵌套结构,jsonrepair
有时也会无能为力。这时可以退一步——用更宽松的解析器,比如 JSON5
:
const JSON5 = require('json5');
const result = JSON5.parse("{name: 'John', age: 30}"); // 也能解析
我看着认真学习的小语,语重心长的讲道:它不是修复,而是扩展 JSON 标准,让一些非标准写法也能解析(JSON5 能容忍的内容包括:单引号、尾逗号、注释、未加引号的属性名、十六进制、科学计数法等数字格式),
接着我们还讨论了更复杂的修复方式,比如用正则处理批量日志,甚至用 AST 工具逐步构建 JSON 树。但那是更远的故事了。
面对当前的问题,我们准备搞一套组合拳:
function parseJson(jsonString) {
// 第一步:尝试标准JSON解析
try {
return JSON.parse(jsonString);
} catch (e) {
console.log("标准JSON解析失败,尝试修复...");
// 第二步:尝试使用jsonrepair修复
try {
const { jsonrepair } = require('jsonrepair');
const fixedJson = jsonrepair(jsonString);
return JSON.parse(fixedJson);
} catch (e2) {
console.log("修复失败,尝试使用JSON5解析...");
// 第三步:尝试使用JSON5解析
try {
const JSON5 = require('json5');
return JSON5.parse(jsonString);
} catch (e3) {
// 最后:如果所有方法都失败,返回错误信息
console.error("所有解析方法都失败了:", e3);
throw new Error("无法解析JSON数据");
}
}
}
}
结局
一段时间后,小语在前端监控日志里贴了段截图:原本一天上千条的 parse error
错误,几乎消失了。
她补了一句:“终于不用再一个个点开调日志了。”
我回头看她的工位,屏幕亮着,浏览器里是一个模拟器页面,console 正在缓缓输出内容。
她突然抬起头看着我,问道:“AST是什么?听说也能实现json修复?”
来源:juejin.cn/post/7506754146894168118
Spring之父:自从我创立了 Spring Framework以来,我从未如此确信需要一个新项目
大家好,这里是小奏,觉得文章不错可以关注公众号小奏技术
Spring框架之父再出发:发布JVM智能体框架Embabel,赋能企业级AI应用
当今,人工智能的浪潮正以前所未有的势头席卷技术世界,Python
凭借其强大的生态系统成为了AI
开发的“通用语”。
然而,Spring
框架的创始人Rod Johnson
却发出了不同的声音。
”自从我创立 Spring 框架以来,我从未如此坚信一个新项目的必要性。自从我开创了依赖注入(Dependency Injection)和其他 Spring 核心概念以来,我从未如此坚信一种新编程模型的必要性,也从未如此确定它应该是什么样子“
为此,他亲手打造并开源了一个全新的项目——Embabel:一个为 JVM 生态量身定制的 AI 智能体(Agent)框架
我们为什么需要一个智能体框架
难道大型语言模型(LLM)还不够聪明,无法直接解决我们的问题吗?难道多聊天协议(MCP)工具不就是我们让它们解决复杂问题所需要的一切吗?
不。MCP 是向前迈出的重要一步,Embabel
自然也拥抱它,就像它让使用多模型变得简单一样。
但是,我们需要一个更高级别的编排技术,尤其是对于业务应用程序,原因有很多。以下是一些最重要的原因
- 可解释性(Explainability): 在解决问题时,选择是如何做出的?
- 可发现性(Discoverability): MCP 绕开了这个重要问题。我们如何在每个节点找到正确的工具,并确保模型在它们之间进行选择时不会混淆?
- 混合模型的能力(Ability to mix models): 这样我们就不用依赖于“上帝模型”,而是可以为许多任务使用本地的、更便宜的、私有的模型。
- 在流程的任何节点注入“护栏”(guardrails)的能力。
- 管理流程执行并引入更高弹性的能力。
- 大规模流程的可组合性(Composability)。 我们很快将看到的不仅是在一个系统上运行的智能体,而是智能体的联邦。
- 与敏感的现有系统(如数据库)进行更安全的集成,在这些地方,即使是最好的 LLM,给予其写权限也是危险的。
这些问题在企业环境中尤为突出,它们需要的不是一个简单的问答机器人,而是一个可解释、可控制、可组合且足够安全的高级编排系统。这正是智能体框架的价值所在。
为什么是JVM,而不是Python?
Python
在 AI
研究和数据科学领域地位稳固,但 GenAI
的核心是连接与整合。当我们构建企业级 AI
应用时,真正的挑战在于如何将 AI
能力与数十年积累的、运行在 JVM
上的海量业务逻辑、基础设施和数据无缝对接。
在企业应用开发、复杂系统构建和关键业务逻辑承载方面,JVM 生态(Java/Kotlin)拥有无与伦比的优势和成熟度。因此,与其让业务逻辑去追赶 AI 技术栈,不如让 AI 技术栈主动融入业务核心——JVM。
Embabel:为超越而生的下一代智能体框架
Embabel 的目标并非简单地追赶 Python
社区的同类框架,而是要实现跨越式超越。它带来了几个革命性的特性:
- 确定性的智能规划:Embabel 创新地引入了非 LLM 的 AI 规划算法。它能自动从你的代码中发现可用的“能力”和“目标”,并根据用户输入智能地规划出最优执行路径。这意味着你的系统是可扩展的,增加新功能不再需要重构复杂的逻辑,同时整个规划过程是确定且可解释的。
- 类型安全的领域模型:
Embabel
鼓励开发者使用Kotlin data class
或Java record
构建丰富的领域模型。这使得与 LLM 交互的提示(Prompt)变得类型安全、易于工具检查和代码重构,从根本上提升了代码质量和可维护性。 - 与
Spring
无缝集成:Embabel
用Kotlin
构建,并承诺为Java
开发者提供同等一流的体验。更重要的是,它与Spring
框架深度集成。对于数百万Spring
开发者来说,构建一个 AI 智能体将像开发一个REST API
一样自然、简单。
加入我们,共创未来
对于JVM
开发者来说,这是一个激动人心的时代。Embabel
提供了一个绝佳的机会,让你可以利用自己早已熟练掌握的技能,为你现有的 Java/Kotlin
应用注入强大的 AI 能力,从而释放巨大的商业价值。
项目尚在早期,但蓝图宏大。Embabel
的目标是成为全球最好的智能体平台。现在就去 GitHub
关注 Embabel,加入社区,贡献你的力量,一同构建企业级 AI 应用的未来。
参考
来源:juejin.cn/post/7507438828178849828
苹果加急审核是“绿色通道”还是"死亡陷阱"?
前言
作为iOS的老人基本上都听说过加急审核通道
,对于加急审核通道最初是苹果审核给开发者用来维护重大Bug,提供的特殊通道。
如果是说遇到重大节假日或者有严重闪退问题,比如:新用户注册失败,搜索闪退,启动闪退等等..
有一位粉丝最近遇到了加急审核的时候触发了账号等待终止的通知,具体如下:
We're unable to accommodate your expedite request.
Your developer account is currently pending termination due to violations of the Apple Developer Program License Agreement. Refer to the message you received in App Store Connect for more information.
If you believe your account shouldn't be terminated, you may submit an appeal to the App Review Board if you have not done so already.
加急审核能不能用?
首先刚刚已经提过了关于苹果提供加急审核背景,常言道:存在即合理!
如果说产品确实出现了紧急Bug,真的需要修复没有问题。但是建议新账号新产品不要使用。
这就好比一个新手村的小白,突然跟PNC说我想获得新手通关秘籍。对于苹果审核人员来说只会是满头问号。内心OS:谁给你的勇气?勇气大帝梁静茹么?
正常迭代的产品申请加急审核最快在30~50分钟就可以进入【这里是指从等待审核状态到正在审核状态】。
那么老账户可以使用但是别作! 这里说的别作是指别动不动就加急审核!
尤其是小修小补的bug,想快速开奖。虽然加急审核没有明确的次数说明,但是归根结底加急审核是为开发者提供“绿色通道”,而不是试探苹果底线的通道。
这就好星爷“九品芝麻官”中的场景。
偶尔使用还好,频繁使用对于审核人员的内心。
为什么会触发封号?
说句拗口的话,封号不是因为加速审核而封号,而是本身就有问题而封号。说的直白一点,加速审核不过是导火索罢了。 其实苹果早就盯上了,只是问题不太严重睁一只眼闭一只,本来大家可以相安无事,非得站出来打破这种平衡。
举个栗子,这就好比追一个女神本来处于比较暧昧的关系半推半就,你突然心急了。让女神一下子看到了你的另一面,瞬间关系结束回到冰点。
那么到底有哪些行为开发者不适合加急审核呢?
1.套用的代码,重复使用的矩阵型App
2.新产品使用被下架品牌词做App名称
3.购买的账号,身份信息存在可疑
4.存在关联的测试设备或收款账户
5.过渡堆砌关键词在元数据中
作为开发者要拎的清楚大小王,不要疯狂去试探苹果的规则,除非UI原创、玩法新颖、代码古法手打。
遵守规则,方得长治久安
,最后祝大家大吉大利,今晚过审!
来源:juejin.cn/post/7507198934159835174
这篇 Git 教程太清晰了,很多 3 年经验程序员都收藏了
引言
📌 Git 是现代开发中不可或缺的版本控制工具,尤其适用于团队协作和代码管理。本文将带你了解 Git 的基础操作命令,包括 git init
、git add
、git commit
、git diff
、git log
、.gitignore
等,快速上手版本控制。
🛠️ 一、初始化仓库:git init
使用 Git 前,需先初始化一个本地仓库:
git init
执行后会在当前目录生成一个 .git
文件夹,Git 会在此目录下跟踪项目的变更记录。
👤 二、配置用户信息
首次使用 Git 时,推荐设置用户名和邮箱:
git config --global user.name "xxxxx"
git config --global user.email "xxxx@qq.com"
加上 --global
会全局生效,仅对当前项目配置可以省略该参数。
📦 三、代码暂存区(Staging Area)是什么?
Git 的提交操作分为两个阶段:暂存(staging) 和 提交(commit) 。
- 当你修改了文件,Git 并不会立即记录这些改动;
- 你需要先使用
git add
命令,把改动“放进暂存区”,告诉 Git:“这些改动我准备好了,可以提交”; - 然后再使用
git commit
将暂存区的内容提交到本地仓库,记录为一个快照。
🧠 可以把暂存区类比为“快照准备区”,你可以反复修改文件、添加到暂存区,最后一口气提交,确保每次提交都是有意义的逻辑单元。
🎯 举个例子:
# 修改了 index.html 和 style.css
git add index.html # 把 index.html 放入暂存区
git add style.css # 再把 style.css 放入暂存区
git commit -m "更新首页结构和样式" # 一起提交
💡 小贴士:你可以分批使用 git add
管理暂存内容,按逻辑分组提交更利于协作和回溯。
📝 四、查看当前状态:git status
在进行任何修改之前,查看当前仓库的状态是非常重要的。git status
是最常用的命令之一,能让你清楚了解哪些文件被修改了,哪些文件已加入暂存区,哪些文件未被跟踪。
git status
它的输出通常会分为三部分:
- 已暂存的文件:这些文件已使用
git add
添加到暂存区,准备提交。 - 未暂存的文件:这些文件被修改,但还未添加到暂存区。
- 未跟踪的文件:这些文件是新创建的,Git 并未跟踪它们。
例如:
On branch main
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: index.html
new file: style.css
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: app.js
🎯 通过 git status
,你可以随时了解当前工作区和暂存区的状态,帮助你决定接下来的操作。
📥 五、添加文件到暂存区:git add
当你修改或新增文件后,使用 git add
将其添加到 Git 的暂存区:
git add 文件名
也可以批量添加所有修改:
git add .
💾 六、提交更改:git commit -m
将暂存区的内容提交至本地仓库:
git commit -m "提交说明"
-m
后面是提交信息,建议语义清晰,例如:
git commit -m "新增用户登录功能"
🚀 七、推送到远程仓库:git push origin main
本地提交之后,需要推送代码到远程仓库(如 GitHub、Gitee):
git push origin main
origin
是默认的远程仓库别名;main
是目标分支名(如果你使用的是master
,请替换);
✅ 提交后远程成员就可以拉取(pull)你最新的修改了。
🔗 如果你还没有远程仓库,请先去 GitHub / Gitee 创建一个,然后关联远程仓库地址:
git remote add origin https://github.com/yourname/your-repo.git
🕵️ 八、查看文件改动:git diff
在 commit
之前,可用 git diff
查看修改内容:
git diff
📜 九、查看提交历史:git log --oneline
快速查看历史提交记录:
git log --oneline
输出示例:
e3f1a1b 添加登录功能
2c3d9a7 初始提交
🛑 十、忽略某些文件:.gitignore
在项目中,有些文件无需提交到 Git 仓库,例如缓存、编译结果、配置文件等。使用 .gitignore
文件可忽略这些文件:
# 忽略 node_modules 文件夹
node_modules/
# 忽略所有 .log 文件
*.log
# 忽略 .env 环境变量文件
.env
🌿 十一、重命名默认分支:git branch -M main
很多平台(如 GitHub)推荐使用 main
作为主分支名称:
git branch -M main
这样可以将默认分支由 master
改为 main
。
✅ 总结命令一览表
命令 | 作用 |
---|---|
git init | 初始化仓库 |
git config | 设置用户名与邮箱 |
git status | 查看当前文件状态 |
git add | 添加改动到暂存区 |
git commit -m | 提交改动 |
git push origin main | 推送代码到远程 main 分支 |
git diff | 查看未提交的改动 |
git log --oneline | 查看提交历史 |
.gitignore | 忽略文件 |
git branch -M main | 重命名分支为 main |
🧠 写在最后
Git 是每个开发者都必须掌握的技能之一。掌握好这些常用命令,就能覆盖 90% 的使用场景。未来如果你要进行多人协作、分支合并、冲突解决,这些基础就是你的武器库。
觉得实用就点个赞、关注、收藏支持一下吧 🙌
来源:juejin.cn/post/7506776151315922971
📢 程序员注意!这些代码可能会让你"吃牢饭"!
关注我的公众号:【编程朝花夕拾】,可获取首发内容。
01 引言
不知道你有没有听过面向监狱编程,可能你好好的码着代码,就就被帽子叔叔带走了。
"我只是个写代码的,关我什么事?" 这是深圳某P2P平台架构师在法庭上崩溃大喊,但代码提交记录中的"反爬优化注释"成了铁证——他因非法吸收公众存款罪获刑5年。这不是电影,而是2023年真实判决!
程序员早已不是免责职业,你的键盘可能正在敲响监狱的大门!
02 血泪案例
⚠️ 这些代码真的会"坐牢"!
2.1 爬虫爬进铁窗
爬虫其实是最容易爬进铁窗的代码。当时可能只是为了解决一个业务痛点,一旦被非法使用,就可能触犯法律的红线。开发者无意,使用者有心,莫名其名的就背了锅。
案例:
浙江某程序员用分布式爬虫狂扫10亿条个人信息,庭审时辩解:"技术无罪!"。
法官怒怼:"每秒突破5次反爬验证,这叫'技术中立'?" —— 6人团队全员获刑!
所以,我们在日常工作中开发爬虫就应该得到启示:
- ✅数据是否涉个人隐私?
- ✅是否突破网站防护?
- ✅对方是否知情同意?
实在拿不准,公司一般都会有法务,可以咨询。
2.2 权限变"凶器"
我们在开发过程中为处理决异常数据的问题,可能会在代码里面留后门。正常的业务功能本身没有问题,但是涉及支付、数据安全等行为,就要注意了。被他人恶意使用,不仅会造成财产损失,可能会还会勉励牢狱之灾。
案例:
杭州前程序员偷偷植入"定时转账代码",21万公款秒变私人财产。检察机关以马某涉嫌盗窃罪、妨害公务罪向法院提起公诉,经法院审理作出一审判决,马某被判处有期徒刑四年二个月,并处罚金。
该起事件也为程序员们敲响了警钟。玩归玩,闹归闹,法律红线不可碰。
🛑 高位操作清单:
- ❌ 私留系统后门
- ❌ 超权限访问数据
- ❌ 删除/篡改数据或日志
2.3 "技术黑产"陷阱
程序员除了工作之外,很多人可能还会通过接私活,如猪八戒网等。以此增加自己的收入。没有公司的严格审核,很多程序员就会掉如技术黑产 的陷阱。
案例:
湖北大学生接私活开发"诈骗APP",庭审播放需求录音:"要能后台改赌局结果"。可能当时你只想着:"我只负责技术实现" 最后却成诈骗案从犯!
尤其一些关于支付的似乎要尤为谨慎,特别是支付成功后,限制体现的时间,很有可能就会用于非法洗钱的黑坑里。
🔥 接私活避坑指南
- 👉 要求签署书面合同
- 👉 录音确认需求合法性
- 👉 转账账户必须对公
03 为什么程序员总成背锅侠
其实大多数程序员都是很单纯的,尤其那些喜欢挑战的程序员。他可能只为表现自己的实力,仅仅只是按照需求开发了功能,但是被恶意利用,最终成为背锅侠
程序员以为 | 法官认定 |
---|---|
突破反爬是技术挑战 | 非法侵入计算机系统 |
按需求开发无过错 | 明知违法仍提供帮助 |
删除代码就没事 | 电子证据完整链锁定" |
血泪真相:你的Git提交记录、代码注释、甚至TODO列表都可能成为呈堂证供!
04 IT人保命三件套
4.1 代码防坐牢套餐
敏感功能增加法律注释。开发的功能以及项目的沟通都要留档尤其需求的变更。因为接到需求的时候可能没有问题,改着改着就出问题了。
拒绝口头需求,落实文档记录,需求、会议、项目事件以邮件的方式存档。
4.2 权限管理生死线
权限管理是保护数据安全的重要措施,但是可能为了调试方便,预留逃逸后门。被人利用轻则数据信息泄露、重则踩缝纫机。
三方对接中,加强公钥、私钥的管理,防止恶意推送或者拉取数据。敏感信息是否脱敏,都是开发中需要考虑的要点。
如果有必要,增加埋点记录,日志记录。收集用户的操作行为。
4.3 法律意识
每个IT公司都会面临网络安全的检查,可以多了解一些相关的法律发条。至少了解那些数据可能会属于需要保护的数据,引起重视。
如果公司允许,可以多参加一些《数据安全法》《网络安全》等的培训。
05 技术向善
代码改变世界,这个不是一句虚话。运用好技术,代码也可以是光。
阿里巴巴的支付宝硬是借助技术,将全国带入数字支付的时代;疫情期间的随申码、一码通等,为战胜疫情作出了巨大贡献;大模型的火爆推送了智能时代的到来等等。
真正的大神不仅代码能跑通,人生更不能"跑偏"!
你在工作中遇到过哪些"法律边缘"的需求?评论区说出你的故事。
来源:juejin.cn/post/7506417928788836362
Flutter - 聊天键盘与面板丝滑切换的强势升级 🍻
欢迎关注微信公众号:FSA全栈行动 👋
BiliBili: http://www.bilibili.com/video/BV1yT…
一、概述
距离 chat_bottom_container 首个可用版本 (0.0.2
) 的发布已经过去了 1
个多月,在这期间根据大家的使用反馈,我们也做了一些优化调整,今天就来盘点一下到底做了哪些优化,新增了什么功能,以及一些常见操作。
请注意
- 本篇仅介绍更新的优化及功能,基础使用请查看: Flutter - 实现聊天键盘与功能面板的丝滑切换 🍻
- 截至本篇发布时,最新版本为
0.2.0
,可以关注我们的微信公众号 FSA全栈行动 获取最新的资讯
开源不易,如果你也觉得这个库好用,请不吝给个
Star
👍 ,并多多支持!
二、使用
调整键盘高度监听管理逻辑
0.1.0
版本前,只考虑了页面栈这种常规情况,当键盘高度变化时只处理栈顶的监听。
但其实还有一种常见打破该规则的场景,就是悬浮聊天页,它会一直在页面上,可能为了能快速从悬浮小球展开聊天页面,收起时只是做了隐藏,而不会销毁页面,在这种情况下,它依旧在监听管理里的栈顶,所以在收起后,上一个聊天页的键盘高度监听就会失效。
这个在 0.1.0
版本中得到修复,内部会倒序遍历调用所有的监听回调。
不过你不用担心这一改动会导致其它聊天页面出现多余的视图刷新,因为在键盘高度监听回调里会先判断输入框是否有焦点,若无则直接返回了。
兼容外接键盘
当连接外接键盘时,软键盘会消失,高度会降为 0
,这里可以用 iOS
模拟器结合 Toggle Software Keyboard
(快捷键: cmd + k
) 来模拟连接与断开外接键盘的效果。
隐藏面板
有小伙伴提出,不知道如何程序式的隐藏面板,其实很简单,就两步
- 让输入框失去焦点
- 更新内部状态为
ChatBottomPanelType.none
hidePanel() {
// 0.2.0 前
inputFocusNode.unfocus();
if (ChatBottomPanelType.none == controller.currentPanelType) return;
controller.updatePanelType(ChatBottomPanelType.none);
// 0.2.0 后,可以这么写
controller.updatePanelType(
ChatBottomPanelType.none,
forceHandleFocus: ChatBottomHandleFocus.unfocus,
);
}
自定义底部安全区高度
在默认情况下,chat_bottom_container
在收起模式 (.none
) 下会自动帮你添加底部安全区高度,但在一些场景下你可能不希望如此。比如:
- 安卓的底部安全区的高度,很多小伙伴都是简单粗暴的设置个高度了事
App
首页有底部BottomNavigationBar
,不需要安全区高度
在此,你可以通过将 safeAreaBottom
参数来自定义这个高度,如下设置为 0
。
return ChatBottomPanelContainer<PanelType>(
...
safeAreaBottom: 0,
...
);
调整键盘面板高度
如示例中位于首页的聊天页面

在键盘弹出时,如下图所示
实际 | 期望 |
---|---|
![]() | ![]() |
很明显,我们希望键盘容器高度能够减去外层底部固定的 BottomNavigationBar
高度。
ChatBottomPanelContainer
提供了 changeKeyboardPanelHeight
回调,在回调中可以拿到当前的键盘高度,经过计算后,将合适的键盘容器高度返回即可。
return ChatBottomPanelContainer<PanelType>(
...
changeKeyboardPanelHeight: (keyboardHeight) {
final renderObj = bottomNavigationBarKey.currentContext?.findRenderObject();
if (renderObj is! RenderBox) return keyboardHeight;
return keyboardHeight - renderObj.size.height;
},
...
);
缓存键盘高度
先来看未做键盘高度缓存处理之前,会发生什么?
上图一共进入了三次聊天页
- 第一次是先点击键盘,再切到表情面板,体验起来还是挺不错的。
- 为了避免一闪而过,没有注意到,所以第二次和第三次的操作是一样的,先唤起表情面板,再切到键盘,可以看到在切到键盘时会抖动。
这是因为每次进入聊天页,键盘的高度为初始值 0
,在 0.2.0
版本中对此进行了优化,加入了键盘高度缓存逻辑,从而尽量避免该抖动问题的出现。
❗️ 但需要注意的是,假如你卸载重装
App
,该缓存会丢失,即你还是有可能会看到最多一次的抖动。
除此之外,你还可以使用这个缓存的键盘高度来实现表情面板与键盘高度保持一致的效果,这样在切换的时候体验上会更好些。😉
Widget _buildEmojiPickerPanel() {
// 如果键盘高度还没有缓存过,则使用默认高度 300
double height = 300;
final keyboardHeight = controller.keyboardHeight;
if (keyboardHeight != 0) {
height = keyboardHeight;
}
return Container(
padding: EdgeInsets.zero,
height: height,
color: Colors.blue[50],
child: const Center(
child: Text('Emoji Panel'),
),
);
}
效果如下
支持表情面板与输入框焦点共存
这也是提升用户体验的重要一点,效果见上图。
先按如下设置你的输入框
bool readOnly = false;
TextEditingController textEditingController = TextEditingController();
...
TextField(
controller: textEditingController,
focusNode: inputFocusNode,
// 为 true 时不显示键盘,默认为 false
readOnly: readOnly,
// 获取焦点后显示光标,设置为 true 才不受 readOnly 的影响
showCursor: true,
),
...
接下来就是切换表情面板的操作
switchToEmojiPanel() {
readOnly = true;
// 这里你可以只刷新输入框
setState(() {});
// 等待下一帧
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
controller.updatePanelType(
// 内部切至 other 状态
ChatBottomPanelType.other,
// 关联外部的面板类型为表情面板
data: PanelType.emoji,
// 输入框获取焦点
forceHandleFocus: ChatBottomHandleFocus.requestFocus,
);
});
}
在 updatePanelType
方法中,如果是切至 .other
状态,是会帮你执行失去焦点操作的,所以这里提供了一个 forceHandleFocus
参数,如果你对方法内部对焦点的处理不满意,你可以使用它来强制指定焦点的处理方式。
三、最后
好了,上述便是该库的更新内容, 惯例附上 GitHub
地址: github.com/LinXunFeng/… ,如果接入上有什么问题,可以在链接中查看 demo
演示代码。
开源不易,如果你也觉得这个库好用,请不吝给个 Star
👍 ,并多多支持!
本篇到此结束,感谢大家的支持,我们下次再见! 👋
如果文章对您有所帮助, 请不吝点击关注一下我的微信公众号:FSA全栈行动, 这将是对我最大的激励. 公众号不仅有
iOS
技术,还有Android
,Flutter
,Python
等文章, 可能有你想要了解的技能知识点哦~
来源:juejin.cn/post/7399045497002328102
一个js库带你看懂AI+前端的发展方向
前言
随着技术的发展,人工智能正逐渐渗透到我们生活的方方面面,从前端开发到后端服务,从数据分析到用户体验设计。特别是在前端领域,AI 的应用正成为一个不可忽视的趋势。本文将探讨 AI 在前端领域的应用,并重点介绍一个在浏览器端即可运行的神经网络库——Brain.js。
Brain.js:浏览器端的神经网络库
Brain.js 是一个专为前端开发者设计的 JavaScript 库,它允许开发者在浏览器或 Node.js 环境中轻松创建和训练神经网络。以下是 Brain.js 的几个核心能力:
- 投喂数据训练
- Brain.js 支持以 JSON 数组的形式投喂数据,这使得准备训练数据变得非常简单。例如,可以准备一个包含输入和期望输出的数据集,用于训练神经网络。
const trainingData = [
{ input: [0, 0], output: { zero: 1 } },
{ input: [0, 1], output: { one: 1 } },
{ input: [1, 0], output: { one: 1 } },
{ input: [1, 1], output: { zero: 1 } }
];
- 实例化神经网络
- Brain.js 提供了多种类型的神经网络,包括前馈神经网络(Feedforward Neural Networks)和循环神经网络(Recurrent Neural Networks)。对于文本处理和序列数据,推荐使用
brain.recurrent.LSTM()
,这是一种长短期记忆网络,特别适合处理时间序列数据。
- 训练模型
- 训练神经网络非常简单,只需调用
train
方法并传入训练数据即可。Brain.js 会自动调整网络参数,使模型逐步学会从输入数据中提取特征并作出准确的预测。
- 推理能力
- 训练完成后,可以使用
run
方法对新的输入数据进行推理。例如,在 NLP 场景中,可以使用训练好的模型对用户输入的文本进行情感分析或分类。
- 结果分类
- Brain.js 支持多分类任务,可以将输入数据归类到多个预定义的类别中。这对于内容推荐、垃圾邮件过滤等应用场景非常有用。
开始使用 Brain.js:
要开始使用 Brain.js,首先需要安装它。如果你是在 Node.js 环境下工作,可以通过 npm 安装:
npm install brain.js
如果你在浏览器中使用,可以直接通过 CDN 引入:
<script src="https://cdn.jsdelivr.net/npm/brain.js"></script>
然后可以按照官方文档提供的示例代码来构建你的第一个神经网络模型。
示例1:
// 创建一个神经网络
const network = new brain.NeuralNetwork();
// 用 4 个输入对象训练网络
network.train([
{ input: [0, 0], output: { zero: 1 } },
{ input: [0, 1], output: { one: 1 } },
{ input: [1, 0], output: { one: 1 } },
{ input: [1, 1], output: { zero: 1 } }
]);
// [1, 0] 的预期输出是什么?
const result = network.run([1, 0]);
// 显示 "zero" 和 "one" 的概率
console.log(result["one"] + " " + result["zero"]);
- 使用
new brain.NeuralNetwork()
创建一个神经网络。 - 使用
network.train([examples])
训练网络。 examples
表示 4 个输入值及其对应的输出值。- 使用
network.run([1, 0])
询问 "[1, 0] 的可能输出是什么?"
网络的输出是:
- one: 93%(接近 1)
- zero: 6%(接近 0)
使用 CSS,颜色可以通过 RGB 设置:
示例2:
颜色 | RGB |
---|---|
黑色 | RGB(0,0,0) |
黄色 | RGB(255,255,0) |
红色 | RGB(255,0,0) |
白色 | RGB(255,255,255) |
浅灰色 | RGB(192,192,192) |
深灰色 | RGB(65,65,65) |
下面的代码展示了如何预测颜色的深浅:
// 创建一个神经网络
const net = new brain.NeuralNetwork();
// 用 4 个输入对象训练网络
net.train([
// 白色 RGB(255, 255, 255)
{ input: [255 / 255, 255 / 255, 255 / 255], output: { light: 1 } },
// 浅灰色 (192, 192, 192)
{ input: [192 / 255, 192 / 255, 192 / 255], output: { light: 1 } },
// 深灰色 (64, 64, 64)
{ input: [65 / 255, 65 / 255, 65 / 255], output: { dark: 1 } },
// 黑色 (0, 0, 0)
{ input: [0, 0, 0], output: { dark: 1 } }
]);
// 深蓝色 (0, 0, 128) 的预期输出是什么?
let result = net.run([0, 0, 128 / 255]);
// 显示 "dark" 和 "light" 的概率
console.log(result["dark"] + " " + result["light"]);
- 使用
new brain.NeuralNetwork()
创建一个神经网络。 - 使用
network.train([examples])
训练网络。 examples
表示 4 个输入值及其对应的输出值。- 使用
network.run([0, 0, 128 / 255])
询问 "深蓝色的可能输出是什么?"
网络的输出是:
- Dark: 95%
- Light: 4%
示例3:
下面这个例子演示如何使用 Brain.js 创建并训练一个基本的神经网络,该网络学习从摄氏度转换为华氏度:
const brain = require('brain.js');
// 创建一个 LSTM 神经网络实例
const net = new brain.recurrent.LSTM();
// 准备训练数据
const trainingData = [
{ input: '0', output: '32' }, // 0°C -> 32°F
{ input: '100', output: '212' } // 100°C -> 212°F
];
// 训练神经网络
net.train(trainingData, {
iterations: 20000, // 训练迭代次数
log: (stats) => console.log(`Training progress: ${stats.iterations}/${stats.error}`) // 训练日志
});
// 使用训练好的模型进行推理
const output = net.run('50'); // 预测 50°C 对应的华氏温度
console.log(output); // 输出结果接近 "122"
其他用于创建神经网络的js库
TensorFlow.js、Synaptic.js、ConvNetJS、Keras.js、Deeplearn.js (现更名为 TensorFlow.js)、 ML.js等。
这些js库作为在浏览器端即可运行的神经网络库,为前端开发者提供了强大的工具,使得我们能够在不深入数学和机器学习理论的前提下,快速实现和应用机器学习功能。无论是简单的分类任务、预测建模,还是更复杂的自然语言处理和图像识别,它们都能帮助你轻松应对。
结语
你发现了吗,通过brain.js,你也可以轻松地将机器学习功能集成到你的项目中。未来,随着模型的小型化、边缘计算的发展以及多模态融合的推进,AI + 前端将更加普及和成熟。
点个赞再走吧~
来源:juejin.cn/post/7438876948762066980