Flutter 最佳实践和编码准则

Flutter 最佳实践和编码准则

视频

前言

最佳实践是一套既定的准则,可以提高代码质量、可读性和可靠性。它们确保遵循行业标准,鼓励一致性,并促进开发人员之间的合作。通过遵循最佳实践,代码变得更容易理解、修改和调试,从而提高整体软件质量。

原文 https://ducafecat.com/blog/flutter-best-practices-and-coding-guidelines

参考

https://dart.dev/effective-dart/style

正文开始

有许多准则和实践可以采用来提高代码质量和应用性能。

Naming convention 命名规范

  • 类、枚举、类型定义、混入和扩展的名称应使用大驼峰命名法。
# Good 
class ClassName {}

extension ExtensionName on String {}

enum EnumName {}

mixin MixinName{}

typedef FunctionName = void Function();
# Bad
class Classname {
}

extension Extensionname on String {
}

enum Enumname {
}
mixin Mixinname{}

typedef Functionname = void Function();
  • Libraries、包、目录和源文件的名称应该使用蛇形命名法(小写字母加下划线)。
# Good 
my_package
└─ lib
   └─ bottom_nav.dart

# Bad 
mypackage
└─ lib
   └─ bottom-nav.dart
  • 导入的前缀命名应该使用蛇形命名法(小写字母加下划线)。
# Good 
import 'package:dio/dio.dart' as dio;

#Bad 
import 'package:dio/dio.dart' as Dio;
  • 变量、常量、参数和命名参数应该使用小驼峰命名法。
# Good
int phoneNumber;
const pieValue=3.14;

// parametrs
double calculateBMI(int weightInKg, int heightInMeter) {
  return weightInKg / (heightInMeter * heightInMeter);
}

//named parametrs
double calculateBMI({int? weightInKg, int? heightInMeter}) {
  if(weightInKg !=null && heightInMeter !=null){
  return weightInKg / (heightInMeter * heightInMeter);
   }
}
# Bad 
int phone_number;
const pie_value=3.14;

// parametrs
double calculateBMI(int weight_in_kg, int height_in_meter) {
  return weight_in_kg / (height_in_meter * height_in_meter);
}

//named parametrs
double calculateBMI({int? weight_in_kg, int? height_in_meter}) {
  return weight_in_kg / (height_in_meter * height_in_meter);
}
  • 应该遵循适当有意义的命名规范。
# Good
Color backgroundColor;
int calculateAge(Date dob);

# Bad
Color bg;
int age(Date date);
  • 私有变量名前面加下划线。
class ClassName {

// private variable
String _variableName;
}

使用可空运算符

在处理条件表达式时,建议使用 ?? (如果为null)和 ?. (null aware)运算符,而不是显式的null检查。 ?? (如果为空)运算符:

# Bad
String? name;
name= name==null ? "unknown": name;

# Good
String? name;
name= name ?? "unknown";

?. (空值安全)运算符:

# Bad
String? name;
name= name==nullnull: name.length.toString();

# Good
String? name;
name=name?.length.toString();

为了避免潜在的异常情况,在Flutter中建议使用 is 运算符而不是 as 强制转换运算符。 is 运算符允许更安全地进行类型检查,如果转换不可能,也不会抛出异常。

# Bad
(person as Person).name="Ashish";

# Good 
if(person is Person){
  person.name="Ashish";
}

避免不必要地创建lambda函数

Lambda 函数(也称为匿名函数或闭包)是一种无需声明函数名称即可定义的函数。它是一种简洁、灵活的函数编写方式,通常用于需要传递函数作为参数或以函数作为返回值的语言特性中。

在 Dart 和许多其他编程语言中,Lambda 函数可以使用箭头语法或 () {} 语法来定义。例如,在 Dart 中,下面的代码演示了如何使用箭头语法定义一个 lambda 函数:在可以使用 tear-off 的情况下,避免不必要地创建 lambda 函数。如果一个函数只是简单地调用一个带有相同参数的方法,就没有必要手动将调用包装在 lambda 函数中。

# Bad
void main(){
  List<int> oddNumber=[1,3,4,5,6,7,9,11];
  oddNumber.forEach((number){
   print(number);
  });
}
# Good 
void main(){
  List<int> oddNumber=[1,3,4,5,6,7,9,11];
  oddNumber.forEach(print);
}

使用扩展集合简化您的代码

  • 当你已经在另一个集合中存储了现有的项目时,利用扩展集合可以简化代码。
# Bad 
  List<int> firstFiveOddNumber=[1,3,5,7,9];
  List<int> secondFiveOddNumber=[11,13,15,17,19];
  firstFiveOddNumber.addAll(secondFiveOddNumber);

# Good
  List<int> secondFiveOddNumber=[11,13,15,17,19];
  List<int> firstFiveOddNumber=[1,3,5,7,9,...secondFiveOddNumber];

使用级联操作简化对象操作

  • Cascades(级联)操作符非常适合在同一对象上执行一系列操作,使代码更加简洁易读。
class Person {
  String? name;
  int? age;
  Person({
    this.name,
    this.age,
  });

  @override
  String toString() {
    return "name: $name age $age";
  }
}
# Bad 

void main(){
 final person=Person();
  person.name="Ashish";
  person.age=25;
  print(person.toString());
}


# Good 

void main(){
 final person=Person();
  person
    ..name="Ashish"
    ..age=25;
  print(person.toString());
}

使用if条件在行和列中实现最佳widget 渲染

  • 在根据行或列中的条件渲染widget 时,建议使用if条件而不是可能返回null的条件表达式。
# Bad
Column(
        children: [
          isLoggedIn
              ? ElevatedButton(
                  onPressed: () {},
                  child: const Text("Go to Login page"),
                )
              : const SizedBox(),
        ],
      ),
# Good
Column(
        children: [
         if(isLoggedIn)
           ElevatedButton(
             onPressed: () {},
              child: const Text("Go to Login page"),
            )
        ],
      ),

使用箭头函数

  • 如果一个函数只有一条语句,使用 () => 箭头函数。
# Bad 
double calculateBMI(int weight_in_kg, int height_in_meter) {
  return weight_in_kg / (height_in_meter * height_in_meter);
}

# Good
double calculateBMI(int weight_in_kg, int height_in_meter) =>
  weight_in_kg / (height_in_meter * height_in_meter);

删除任何打印语句、未使用的和被注释的代码

在 Flutter 中,使用 print 语句来输出调试信息是可行的,但不建议在生产环境中使用,因为它有几个缺点:

  1. 输出的信息可能难以区分:在 Flutter 应用程序中,输出的信息可能会与应用程序本身的输出混杂在一起,这可能会导致输出的信息难以区分。
  2. 输出的信息可能不可靠: print 语句输出的信息通常会被缓存,因此可能不会立即显示出来。这可能会导致在应用程序崩溃之前,无法看到最后一次输出的信息。
  3. 输出的信息可能会影响应用程序性能:在某些情况下,输出的信息可能会大量占用应用程序的资源,影响应用程序的性能。

因此,Flutter 推荐使用专门的日志记录库,如 loggerflutter_bloc 中的 BlocObserver,以便在应用程序中输出可靠、易于区分和可控制的日志。这些库允许您定义输出的日志级别、输出到不同的目标(如控制台或文件)以及格式化日志消息等。例如,使用 logger 库,您可以按以下方式输出日志消息:

# Bad 
# production mode

// commented message---main method   
void main(){
 print("print statement"); 
 //..rest of code
}
void unusedFunction(){
}
# Good 
# production mode
  
void main(){
//..rest of code
}

正确的文件夹结构

  • 将代码分离到适当的文件夹结构中,包括提供者(providers)、模型(models)、屏幕/页面(screens/pages)、服务(services)、常量(constants)和工具(utils)。
project/
  lib/
    providers/
      auth_provider.dart
    models/
      user.dart
    screens/
      home_screen.dart
      login_screen.dart
    utils.dart
    constants.dart
    services.dart
    main.dart
  • 代码格式正确,适当使用 lints 配置。
include: package:flutter_lints/flutter.yaml
analyzer:
  errors:
    require_trailing_commas: error
linter:
  rules:
    require_trailing_commas: true
    prefer_relative_imports: true
  • 尝试通过在 utils 文件夹中保存的辅助函数中实现代码的可重用性。
# utils.dart

import 'package:intl/intl.dart';

String formatDateTime(DateTime dateTime) {
  final formatter = DateFormat('yyyy-MM-dd HH:mm:ss');
  return formatter.format(dateTime);
}
  • widget 还应该被设计成可重复使用的,并可以单独保存在widgets文件夹中。
# text_input.dart
import 'package:flutter/material.dart';

class TextInput extends StatelessWidget {
  final String? label;
  final String? hintText;
  final TextEditingController? controller;
  final TextInputType keyboardType;
  final bool obscureText;
  final StringFunction(String?)? validator;
  final Widget? suffix;

  const TextInput({
    this.label,
    this.hintText,
    this.suffix,
    this.controller,
    this.validator,
    this.obscureText = false,
    this.keyboardType = TextInputType.text,
  });

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      decoration: InputDecoration(
        labelText: label,
        hintText:hintText
        suffixIcon:suffix,
      ),
      controller: controller,
      obscureText: obscureText,
      validator:validator
      keyboardType: keyboardType,
    );
  }
}
  • 在UI界面中避免使用静态或硬编码的字符串,建议根据其范围将其组织在单独的文件夹或文件中。
# Good
# validators/
 common_validator.dart
 
mixin CommonValidator{
 String? emptyValidator(String value) {
    if (value.isEmpty) {
      return 'Please enter';
    } else {
      return null;
    }
  }
}

#config/themes
 colors.dart

class AppColors{
static const white=Color(0xffffffff);
static const black=Color(0xff000000);
}

class LoginPage extends StatelessWidget with CommonValidator  {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: AppColors.black, // good
        title: const Text("Login page"),
      ),
      body: Column(
        children: [
          TextInput(
            label: "email",
            hintText: "email address",
            validator: emptyValidator,  // good 
          )
        ],
      ),
    );
  }
}
#Bad 
class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: const Color(0xff000000), // bad
        title: const Text("Login page"),
      ),
      body: Column(
        children: [
          TextInput(
            label: "email",
            hintText: "email address",
            validator: (value) {   // bad
              if (value!.isEmpty) {
                return 'Please enter';
              } else {
                return null;
              }
            },
          )
        ],
      ),
    );
  }
}

widget 组织

  • 将widget 拆分为不同的widget ,而不是同一个文件。
  • 在widget 中使用const
  • 当在一个State上调用setState()时,所有子孙widget都会重新构建。因此,将widget拆分为小的widget,这样setState()调用只会重新构建那些实际需要改变UI的子树的部分。
# Bad
class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State<LoginPage> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage{
  bool _secureText = true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Login page"),
      ),
      body: Column(
        children: [
          const TextInput(
            label: "Email",
            hintText: "Email address",
          ),
          TextInput(
            label: "Password",
            hintText: "Password",
            obscureText: _secureText,
            suffix: IconButton(
                onPressed: () {
                  setState(() {
                    _secureText = !_secureText;
                  });
                },
                icon: Icon(
                    _secureText ? 
                   Icons.visibility_off 
                   : Icons.visibility)),
          ),
          ElevatedButton(
         onPressed: () {}, 
         child: const Text("Login"))
        ],
      ),
    );
  }
}
# Good

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Login page"),
      ),
      body: Column(
        children: [
          const TextInput(
            label: "Email",
            hintText: "Email address",
          ),
          const TextInput(
            label: "Password",
            hintText: "Password",
            obscureText: true,
          ),
          ElevatedButton(
          onPressed: () {}, 
          child: const Text("Login"))
        ],
      ),
    );
  }
}

//separate TextFormField Component

class TextInput extends StatefulWidget {
  final String? label;
  final TextEditingController? controller;
  final String? hintText;
  final TextInputType keyboardType;
  final StringFunction(String?)? validator;
  final bool obscureText;


  const TextInput({
    super.key,
    this.label,
    this.hintText,
    this.validator,
    this.obscureText = false,
    this.controller,
    this.keyboardType = TextInputType.text,
  });

  @override
  State<TextInput> createState() => _TextInputState();
}

class _TextInputState extends State<TextInput{
  bool _secureText = false;
  @override
  void initState() {
    _secureText = widget.obscureText;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      decoration: InputDecoration(
          labelText: widget.label,
          hintText: widget.hintText,
          suffixIcon: widget.obscureText
              ? IconButton(
                  onPressed: () {
                    setState(() {
                      _secureText = !_secureText;
                    });
                  },
                  icon: Icon(
                    _secureText ? Icons.visibility_off : Icons.visibility,
                    color: Colors.grey,
                  ),
                )
              : null),
      controller: widget.controller,
      validator: widget.validator,
      obscureText: _secureText,
      keyboardType: widget.keyboardType,
    );
  }
}

遵循代码规范

  • 在lib/目录中,避免使用相对导入。请使用包导入。
  • 避免使用 print 打印语句
# Bad
import 'widgets/text_input.dart';

import 'widgets/button.dart'

import '../widgets/custom_tile.dart';

# Good
import 'package:coding_guidelines/widgets/text_input.dart';

import 'package:coding_guidelines/widgets/button.dart'

import 'package:coding_guidelines/widgets/custom_tile.dart';

# Bad
void f(int x) {
  print('debug: $x');
  ...
}

# Good
void f(int x) {
  debugPrint('debug: $x');
}

linter:
  rules:
    - avoid_empty_else
    - always_use_package_imports
    - avoid_print

适当的状态管理

  • 使用Provider作为推荐的状态管理包,但是Riverpod与Provider相似,可以被视为其改进版本。
  • 您还可以选择使用其他状态管理方法,如Bloc、Riverpod、Getx和Redux。
  • 业务逻辑应该与用户界面分离。
# Bad 
class CounterScreen extends StatefulWidget {
  const CounterScreen({
    super.key,
  });
  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen{
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("Counter APP"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
# Good
// separte logic from UI 
// provider state management
class CounterProvider with ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  void incrementCounter() {
    _counter++;
    notifyListeners();
  }

  void decrementCounter() {
    _counter--;
    notifyListeners();
  }
}

// UI 

class CounterScreen extends StatelessWidget {
  const CounterScreen({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("Counter APP"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Consumer<CounterProvider>(
              builder: (context, counter, child) {
                return Text(
                  counter.counter.toString(),
                  style: Theme.of(context).textTheme.headlineMedium,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CounterProvider>().incrementCounter(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

升级第三方包

  • 在应用程序中使用的任何第三方包都需要进行验证,因为有时它可能会破坏构建或与当前的Flutter版本不同步。特别是在升级Flutter时,务必在升级后检查所有插件和第三方包。请确保它们与当前版本兼容。

错误处理和日志记录

  • 使用try-catch块来正确处理代码中的异常和错误。
  • 使用像 pretty_dio_loggerdio_logger 这样的日志记录库来记录重要事件或错误。
# Good 
final dio = Dio()
    ..interceptors.add(PrettyDioLogger(
      requestHeader: true,
      requestBody: true,
      responseBody: true,
      responseHeader: false,
      compact: false,
    ));

Future<dynamic> fetchNetworkData() async{
  try {
    // Simulating an asynchronous network call
    final data= await dio.get('endpoint');
     return data;
  } catch (e, stackTrace) {
    print('An exception occurred: $e');
    print('Stack trace: $stackTrace');
    return e;
   // Perform additional error handling actions
  }
}
# Bad
final dio = Dio();
   
Future<dynamic> fetchNetworkData() {
 dio.get('endpoint').then((data){
    return data;
)}.catchError((e) {
    log.error(e);
    return e;
  });
}

Testing 测试

  • 编写单元测试和widget 测试来确保代码的正确性。
  • 使用像 flutter_test 这样的测试框架来编写和运行测试。
  • 追求高代码覆盖率,尤其是对于应用程序的关键部分。
# Good
// counter app integartion testing
void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('end-to-end test', () {
    testWidgets('tap on the floating action button, verify counter',
        (tester) async {
      app.main();
      await tester.pumpAndSettle();

      // Verify the counter starts at 0.
      expect(find.text('0'), findsOneWidget);

      // Finds the floating action button to tap on.
      final Finder fab = find.byTooltip('Increment');

      // Emulate a tap on the floating action button.
      await tester.tap(fab);

      // Trigger a frame.
      await tester.pumpAndSettle();

      // Verify the counter increments by 1.
      expect(find.text('1'), findsOneWidget);
    });
  });
}

版本控制和协作

  • 使用像Git这样的版本控制系统来跟踪变更并与其他开发者合作。
  • 遵循Git的最佳实践,例如创建有意义的提交信息和分支策略。
The commit type can include the following:

feat – a new feature is introduced with the changes
fix – a bug fix has occurred
chore – changes that do not relate to a fix or feature and don't modify src or test files (for example updating dependencies)
refactor – refactored code that neither fixes a bug nor adds a feature
docs – updates to documentation such as a the README or other markdown files
style – changes that do not affect the meaning of the code, likely related to code formatting such as white-space, missing semi-colons, and so on.
test – including new or correcting previous tests
perf – performance improvements
ci – continuous integration related
build – changes that affect the build system or external dependencies
revert – reverts a previous commit

# Good
feat: button component
chore: change login translation

# Bad
fixed bug on login page
Changed button style
empty commit messages

持续集成与交付

  • 建立一个持续集成(CI)流水线,自动运行测试和检查你的代码库。
  • 控制台可以用 CI services like Jenkins, Travis CI, or GitHub Actions.

写一些文档

  • 使用注释来记录你的代码,尤其是对于复杂或不明显的部分。
  • 请使用描述性和有意义的注释来解释代码片段的目的、行为或用法。
  • 考虑使用Dartdoc等工具生成API文档。

小结

以上的编码准则可以帮助您提高编码标准,增强应用性能,并让您更好地理解最佳实践。通过遵循这些准则,您可以编写更清晰、更易维护的代码,优化应用性能,并避免常见的陷阱。

感谢阅读本文

如果我有什么错?请在评论中让我知道。我很乐意改进。


© 猫哥 ducafecat.com

end

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/53046.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于光子实验的指数级加速的量子同态加密理论

前言 量子计算机不仅有望在某些重要任务上超越经典计算机&#xff0c;而且还能保护计算的隐私。例如&#xff0c;盲量子计算协议支持安全委托量子计算&#xff0c;其中客户端可以保护其数据和算法的隐私&#xff0c;不受分配来运行计算的量子服务器的影响。然而&#xff0c;这…

​《吐血整理》进阶系列教程-拿捏Fiddler抓包教程(9)-Fiddler如何设置捕获Https会话​

1.简介 由于近几年来各大网站越来越注重安全性都改成了https协议&#xff0c;不像前十几年前直接是http协议直接裸奔在互联网。还有的小伙伴或者童鞋们按照上一篇宏哥的配置都配置好了&#xff0c;想大展身手抓一下百度的包&#xff0c;结果一试傻眼了&#xff0c;竟然毛都没有…

Selenium+Java环境搭建(测试系列6)

目录 前言&#xff1a; 1.浏览器 1.1下载Chrome浏览器 1.2查看Chrome浏览器版本 1.3下载Chrome浏览器的驱动 2.配置系统环境变量path 3.验证是否成功 4.出现的问题 结束语&#xff1a; 前言&#xff1a; 这节中小编给大家讲解一下有关于Selenium Java环境的搭建&…

数据结构: 线性表(顺序表实现)

文章目录 1. 线性表的定义2. 线性表的顺序表示:顺序表2.1 概念及结构2.2 接口实现2.2.1 顺序表初始化 (SeqListInit)2.2.2 顺序表尾插 (SeqListPushBack)2.2.3 顺序表打印 (SeqListPrint)2.2.6 顺序表销毁 (SeqListDestroy)2.2.5 顺序表尾删 (SeqListPopBack)2.2.6 顺序表头插 …

晋级榜单揭晓!华秋第九届硬创大赛-华南分赛区路演成功举办

7月21日&#xff0c;第十五届深创赛福田预选赛区暨华秋第九届硬创大赛华南分赛区决赛路演活动在深圳华强科创广场成功举办。活动由深圳华秋电子有限公司&#xff08;以下简称 华秋 &#xff09;、深圳市福田区新一代信息技术产业链党委、深圳新一代产业园、微纳研究院、华强科创…

【嵌入式学习笔记】嵌入式入门1——GPIO

1.什么是GPIO General Purpose Input Output&#xff0c;即通用输入输出端口&#xff0c;简称GPIO&#xff0c;作用是负责采集外部器件的信息或者控制外部器件工作&#xff0c;即输入输出。 2.STM32 GPIO简介 2.1.GPIO特点 不同型号&#xff0c;IO口数量可能不一样&#x…

中小学分班查询系统0成本制作方法公布了,人人可用

传统的学生分班查询平台通常需要进行专业的技术开发&#xff0c;以实现学生查询和查看分班信息的功能。这个过程涉及到软件开发、数据库设计、系统集成等多个环节&#xff0c;需要有一支专业的技术团队来完成。 然而&#xff0c;这样的技术开发和维护过程需要耗费大量的经济成…

HBase有写入数据,页面端显示无数据量

写了一个测试类&#xff0c;插入几条数据&#xff0c;测试HBase的数据量。很简单的功能&#xff0c;这就出现问题了。。网页端可以看到&#xff0c;能够看到读写请求&#xff0c;但是不管是内存、还是磁盘&#xff0c;都没有数据。 于是就想到去HDFS查看&#xff0c;也是有数据…

Python简要复习

Python程序设计复习 Python基础知识 python的特点 兼具编译型和解释型特性&#xff0c;兼顾过程式、函数式和面向对象编程范式的通用编程语言 解释型语言无需像编译型需要一次性的编译成机器码&#xff0c;然后运行&#xff0c;而是由名叫解释器的程序动态的将源代码逐句转…

热备份路由协议原理

热备份路由协议原理 HSRP协议/VRRP协议热备份协议 热备份协议&#xff08;Hot Standby Protocol&#xff09; 是一种基于冗余设计的协议&#xff0c;用于提高网络的可靠性和冗余性。它允许多个设备共享同一个IP地址&#xff0c;其中一个设备被选为主设备&#xff0c;其他设备…

Tinkercad 建模21个小技巧

21个Tinkercad 建模小技巧 原文 参考文章&#xff1a;在 Tinkercad 中加快设计的 22 个技巧 一起来了解一下21个Tinkercad 3D建模小技巧&#xff0c;让你快人一步。 技巧1 Copy & Paste 文件&#xff0c;整合设计 想把文件A里面的模型拷贝到文件B里面&#xff1f; 很容…

【Linux】Centos的一些快捷操作

Centos的一些快捷操作 一个窗口多个终端GVIM 一个窗口多个文件 一个窗口多个终端 GVIM 一个窗口多个文件

idealC-2020.1.4免费下载(附安装教程)

下载地址 [软件名称]: IntelliJ IDEA 2020 [软件大小]: 560MB [安装环境]: Windows [下载链接]: 链接: https://pan.baidu.com/s/1yGOWqfRVE6cPsAe0qHpnbg https://pan.baidu.com/s/1yGOWqfRVE6cPsAe0qHpnbg 提取码: zt88 软件介绍 idea 2020.是由捷克IntelliJ公司而…

python_day16_设计模式

“”“单例模式”“” “”“工厂模式”“” class Person:passclass Worker(Person):passclass Student(Person):passclass Teacher(Person):passclass Factory:def get_person(self, p_type):if p_type w:return Worker()elif p_type s:return Student()else:return Te…

51单片机学习--按键控制流水灯模式定时器时钟

TMOD负责确定T0和T1的工作模式&#xff0c;TCON控制T0和T1的启动或停止计数&#xff0c;同时包含定时器状态 TF1&#xff1a;定时器1溢出标志 TF0&#xff1a;定时器0溢出标志 0~65535 每隔1微秒计数器1&#xff0c;总时间65535微秒&#xff0c;赋上初值64535&#xff0c;则只…

webstorm格式化代码后单引号转成了双引号

webStorm格式化js代码时单引号变成了双引号&#xff0c;问题如下&#xff1a; 格式化前&#xff1a; 格式化后&#xff1a; 解决办法&#xff1a; window: File -> Settings -> Editor -> Code Style -> Javascript&#xff1b; mac: webStorm -> Preference …

Flink回撤流

1.回撤流定义&#xff08;RetractStream&#xff09; Flink 的回撤流是指在 Flink 的流处理算法中&#xff0c;撤回已经发送到下游节点的数据。这是因为在实际应用场景中&#xff0c;有些错误数据可能会发送到下游节点&#xff0c;因此需要回撤流以保证数据的准确性。 回撤流…

【Docker】Docker应用部署之Docker容器安装Tomcat

目录 一、搜索镜像 二、拉取镜像 三、创建容器 四、测试使用 一、搜索镜像 docker search tomcat 二、拉取镜像 docker pull tomcat:版本 三、创建容器 首先在宿主机创建数据卷的目录 mkdir /root/tomcat # 创建目录 cd /root/tomcat # 进入目录 docker run -id -…

【腾讯云 Cloud Studio 实战训练营】永不宕机的IDE,Coding Everywhere

【腾讯云 Cloud Studio 实战训练营】永不宕机的IDE&#xff0c;随时随地写代码&#xff01; 写在最前视频讲解&#xff1a;Cloud Studio活动简介何为腾讯云 Cloud Studio?Cloud Studio简介免费试用&#xff0c;上手无忧Cloud Studio 特点及优势云端开发多种预制环境可选metawo…

怎么学习C语言,才能快速掌握?

有多年软件行业经验&#xff0c;期间参与过多个C语言项目。要掌握一门编程语言&#xff0c;仅仅投入时间学习是不够的&#xff0c;关键在于实际项目经验。在没有真正实战经验之前&#xff0c;不宜轻易声称掌握某种编程语言&#xff0c;因为编程是积累性的工作&#xff0c;理论知…