基于C#+Thrift操作HBase实践

简介:

在基于HBase数据库的开发中,对应Java语言来说,可以直接使用HBase的原生API来操作HBase表数据,当然你要是不嫌麻烦可以使用Thrift客户端Java API,这里有我曾经使用过的 HBase Thrift客户端Java API实践,可以参考。对于具有其他编程语言背景的开发人员,为了获取HBase带来的好处,那么就可以选择使用HBase Thrift客户端对应编程语言的API,来实现与HBase的交互。
这里,我们使用C#客户端来操作HBase。HBase的Thrift接口的定义,可以通过链接http://svn.apache.org/viewvc/hbase/trunk/hbase-server/src/main/resources/org/apache/hadoop/hbase/thrift/Hbase.thrift?view=markup看到,我们需要安装Thrift编译器,才能生成HBase跨语言的API,这里,我使用的版本是0.9.0。需要注意的是,一定要保证,安装了某个版本Thrift的Thrift编译器,在导入对应语言库的时候,版本一定要统一,否则就会出现各种各样的问题,因为不同Thrift版本,对应编程语言的库API可能有变化。
首先,下载上面链接的内容,保存为Hbase.thrift。
然后,执行如下命令,生成C#编程语言的HBase Thrift客户端API:

1 [hadoop@master hbase]$ thrift --gen csharp Hbase.thrift
2 [hadoop@master hbase]$ ls
3 gen-csharp

这里,我们基于C#语言,使用HBase 的Thrift 客户端API访问HBase表。事实上,如果使用Java来实现对HBase表的操作,最好是使用HBase的原生API,无论从性能还是便利性方面,都会提供更好的体验。使用Thrift API访问,实际也是在HBase API之上进行了一层封装,可能初次使用Thrift API感觉很别扭,有时候还要参考Thrift服务端的实现代码。
准备工作如下:

    1. 下载Thrift软件包,解压缩后,拷贝thrift-0.9.0/lib/java/src下面的代码到工作区(开发工具中)
    2. 将上面生成的gen-csharp目录中代码拷贝到工作区
    3. 保证HBase集群正常运行,接着启动HBase的Thrift服务,执行如下命令:
1 bin/hbase thrift -b master -p 9090 start

上面,HBase的Thrift服务端口为9090,下面通过Thrift API访问的时候,需要用到,而不是HBase的服务端口(默认60000)。
接着,实现一个简单的例子,访问Hbase表。
首先,我们通过HBase Shell创建一个表:

1 create 'test_info', 'info'

表名为test_info,列簇名称为info。
然后,我们开始基于上面生成的Thrift代码来实现对HBase表的操作。
这里,我们实际上是对HBase Thrift客户端Java API实践中的Java代码进行了翻译,改写成C#语言的相关操作。我们在客户端,进行了一层抽象,更加便于传递各种参数,抽象类为AbstractHBaseThriftService,对应的命名空间为HbaseThrift.HBase.Thrift,该类实现代码如下所示:

01 using System;
02 using System.Collections.Generic;
03 using System.Linq;
04 using System.Text;
05 using System.Threading.Tasks;
06
07 using Thrift.Transport;
08 using Thrift.Protocol;
09
10 namespace HbaseThrift.HBase.Thrift
11 {
12 public abstract class AbstractHBaseThriftService
13 {
14 protected static readonly string CHARSET = "UTF-8";
15 private string host = "localhost";
16 private int port = 9090;
17 private readonly TTransport transport;
18 protected readonly Hbase.Client client;
19
20 public AbstractHBaseThriftService() : this("localhost", 9090)
21 {
22
23 }
24
25 public AbstractHBaseThriftService(string host, int port)
26 {
27 this.host = host;
28 this.port = port;
29 transport = new TSocket(host, port);
30 TProtocol protocol = new TBinaryProtocol(transport, true, true);
31 client = new Hbase.Client(protocol);
32 }
33
34 public void Open() {
35 if (transport != null)
36 {
37 transport.Open();
38 }
39 }
40
41 public void Close()
42 {
43 if (transport != null)
44 {
45 transport.Close();
46 }
47 }
48
49 public abstract List<string> GetTables();
50
51 public abstract void Update(string table, string rowKey, bool writeToWal,
52 string fieldName, string fieldValue, Dictionary<string, string> attributes);
53 public abstract void Update(string table, string rowKey, bool writeToWal,
54 Dictionary<string, string> fieldNameValues, Dictionary<string, string> attributes);
55
56 public abstract void DeleteCell(string table, string rowKey, bool writeToWal,
57 string column, Dictionary<string, string> attributes);
58 public abstract void DeleteCells(string table, string rowKey, bool writeToWal,
59 List<string> columns, Dictionary<string, string> attributes);
60
61 public abstract void DeleteRow(string table, string rowKey,
62 Dictionary<string, string> attributes);
63
64 public abstract int ScannerOpen(string table, string startRow, List<string> columns,
65 Dictionary<string, string> attributes);
66 public abstract int ScannerOpen(string table, string startRow, string stopRow, List<string> columns,
67 Dictionary<string, string> attributes);
68 public abstract int ScannerOpenWithPrefix(string table, string startAndPrefix,
69 List<string> columns, Dictionary<string, string> attributes);
70 public abstract int ScannerOpenTs(string table, string startRow,
71 List<string> columns, long timestamp, Dictionary<string, string> attributes);
72 public abstract int ScannerOpenTs(string table, string startRow, stringstopRow,
73 List<string> columns, long timestamp, Dictionary<string, string> attributes);
74
75 public abstract List<TRowResult> ScannerGetList(int id, int nbRows);
76 public abstract List<TRowResult> ScannerGet(int id);
77
78 public abstract List<TRowResult> GetRow(string table, string row,
79 Dictionary<string, string> attributes);
80 public abstract List<TRowResult> GetRows(string table,
81 List<string> rows, Dictionary<string, string> attributes);
82 public abstract List<TRowResult> GetRowsWithColumns(string table,
83 List<string> rows, List<string> columns, Dictionary<string, string> attributes);
84
85 public abstract void ScannerClose(int id);
86
87 /**
88 * Iterate result rows(just for test purpose)
89 * @param result
90 */
91 public abstract void IterateResults(TRowResult result);
92
93 }
94 }

这里,简单叙述一下,我们提供的客户端API的基本功能:

  • 建立到Thrift服务的连接:Open()
  • 获取到HBase中的所有表名:GetTables()
  • 更新HBase表记录:Update()
  • 删除HBase表中一行的记录的数据(cell):DeleteCell()和DeleCells()
  • 删除HBase表中一行记录:deleteRow()
  • 打开一个Scanner,返回id:ScannerOpen()、ScannerOpenWithPrefix()和ScannerOpenTs();然后用返回的id迭代记录:ScannerGetList()和ScannerGet()
  • 获取一行记录结果:GetRow()、GetRows()和GetRowsWithColumns()
  • 关闭一个Scanner:ScannerClose()
  • 迭代结果,用于调试:IterateResults()

比如,我们想要实现分页的逻辑,可能和传统的关系型数据库操作有些不同。基于HBase表的实现是,首先打开一个Scanner实例(例如调用ScannerOpen()),返回一个id,然后再使用该id,调用ScannerGetList()方法(可以指定每次返回几条记录的变量nbRows的值),返回一个记录列表,反复调用该ScannerGetList()方法,直到此次没有结果返回为止。后面会通过测试用例来实际体会。
现在,我们基于上抽象出来的客户端操作接口,给出一个基本的实现,代码如下所示:

001 using System;
002 using System.Collections.Generic;
003 using System.Linq;
004 using System.Text;
005 using System.Threading.Tasks;
006
007 namespace HbaseThrift.HBase.Thrift
008 {
009 class HBaseThriftClient : AbstractHBaseThriftService
010 {
011 public HBaseThriftClient() : this("localhost", 9090)
012 {
013
014 }
015
016 public HBaseThriftClient(string host, int port) : base(host, port)
017 {
018
019 }
020
021 public override List<string> GetTables()
022 {
023 List<byte[]> tables = client.getTableNames();
024 List<String> list = new List<String>();
025 foreach(byte[] table in tables)
026 {
027 list.Add(Decode(table));
028 }
029 return list;
030 }
031
032 public override void Update(string table, string rowKey, bool writeToWal,string fieldName, string fieldValue, Dictionary<string, string> attributes)
033 {
034 byte[] tableName = Encode(table);
035 byte[] row = Encode(rowKey);
036 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
037 List<Mutation> mutations = new List<Mutation>();
038 Mutation mutation = new Mutation();
039 mutation.IsDelete = false;
040 mutation.WriteToWAL = writeToWal;
041 mutation.Column = Encode(fieldName);
042 mutation.Value = Encode(fieldValue);
043 mutations.Add(mutation);
044 client.mutateRow(tableName, row, mutations, encodedAttributes);
045 }
046
047 public override void Update(string table, string rowKey, bool writeToWal, Dictionary<string, string> fieldNameValues, Dictionary<string, string> attributes)
048 {
049 byte[] tableName = Encode(table);
050 byte[] row = Encode(rowKey);
051 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
052 List<Mutation> mutations = new List<Mutation>();
053 foreach (KeyValuePair<String, String> pair in fieldNameValues)
054 {
055 Mutation mutation = new Mutation();
056 mutation.IsDelete = false;
057 mutation.WriteToWAL = writeToWal;
058 mutation.Column = Encode(pair.Key);
059 mutation.Value = Encode(pair.Value);
060 mutations.Add(mutation);
061 }
062 client.mutateRow(tableName, row, mutations, encodedAttributes);
063 }
064
065 public override void DeleteCell(string table, string rowKey, bool writeToWal,string column, Dictionary<string, string> attributes)
066 {
067 byte[] tableName = Encode(table);
068 byte[] row = Encode(rowKey);
069 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
070 List<Mutation> mutations = new List<Mutation>();
071 Mutation mutation = new Mutation();
072 mutation.IsDelete = true;
073 mutation.WriteToWAL = writeToWal;
074 mutation.Column = Encode(column);
075 mutations.Add(mutation);
076 client.mutateRow(tableName, row, mutations, encodedAttributes);
077 }
078
079 public override void DeleteCells(string table, string rowKey, bool writeToWal, List<string> columns, Dictionary<string, string> attributes)
080 {
081 byte[] tableName = Encode(table);
082 byte[] row = Encode(rowKey);
083 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
084 List<Mutation> mutations = new List<Mutation>();
085 foreach (string column in columns)
086 {
087 Mutation mutation = new Mutation();
088 mutation.IsDelete = true;
089 mutation.WriteToWAL = writeToWal;
090 mutation.Column = Encode(column);
091 mutations.Add(mutation);
092 }
093 client.mutateRow(tableName, row, mutations, encodedAttributes);
094 }
095
096 public override void DeleteRow(string table, string rowKey, Dictionary<string,string> attributes)
097 {
098 byte[] tableName = Encode(table);
099 byte[] row = Encode(rowKey);
100 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
101 client.deleteAllRow(tableName, row, encodedAttributes);
102 }
103
104 public override int ScannerOpen(string table, string startRow, List<string> columns, Dictionary<string, string> attributes)
105 {
106 byte[] tableName = Encode(table);
107 byte[] start = Encode(startRow);
108 List<byte[]> encodedColumns = EncodeStringList(columns);
109 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
110 return client.scannerOpen(tableName, start, encodedColumns, encodedAttributes);
111 }
112
113 public override int ScannerOpen(string table, string startRow, string stopRow, List<string> columns, Dictionary<string, string> attributes)
114 {
115 byte[] tableName = Encode(table);
116 byte[] start = Encode(startRow);
117 byte[] stop = Encode(stopRow);
118 List<byte[]> encodedColumns = EncodeStringList(columns);
119 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
120 return client.scannerOpenWithStop(tableName, start, stop, encodedColumns, encodedAttributes);
121 }
122
123 public override int ScannerOpenWithPrefix(string table, string startAndPrefix, List<string> columns, Dictionary<string, string> attributes)
124 {
125 byte[] tableName = Encode(table);
126 byte[] prefix = Encode(startAndPrefix);
127 List<byte[]> encodedColumns = EncodeStringList(columns);
128 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
129 return client.scannerOpenWithPrefix(tableName, prefix, encodedColumns, encodedAttributes);
130 }
131
132 public override int ScannerOpenTs(string table, string startRow, List<string> columns, long timestamp, Dictionary<string, string> attributes)
133 {
134 byte[] tableName = Encode(table);
135 byte[] start = Encode(startRow);
136 List<byte[]> encodedColumns = EncodeStringList(columns);
137 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
138 return client.scannerOpenTs(tableName, start, encodedColumns, timestamp, encodedAttributes);
139 }
140
141 public override int ScannerOpenTs(string table, string startRow, stringstopRow, List<string> columns, long timestamp, Dictionary<string, string> attributes)
142 {
143 byte[] tableName = Encode(table);
144 byte[] start = Encode(startRow);
145 byte[] stop = Encode(stopRow);
146 List<byte[]> encodedColumns = EncodeStringList(columns);
147 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
148 return client.scannerOpenWithStopTs(tableName, start, stop, encodedColumns, timestamp, encodedAttributes);
149 }
150
151 public override List<TRowResult> ScannerGetList(int id, int nbRows)
152 {
153 return client.scannerGetList(id, nbRows);
154 }
155
156 public override List<TRowResult> ScannerGet(int id)
157 {
158 return client.scannerGet(id);
159 }
160
161 public override List<TRowResult> GetRow(string table, string row, Dictionary<string, string> attributes)
162 {
163 byte[] tableName = Encode(table);
164 byte[] startRow = Encode(row);
165 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
166 return client.getRow(tableName, startRow, encodedAttributes);
167 }
168
169 public override List<TRowResult> GetRows(string table, List<string> rows, Dictionary<string, string> attributes)
170 {
171 byte[] tableName = Encode(table);
172 List<byte[]> encodedRows = EncodeStringList(rows);
173 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
174 return client.getRows(tableName, encodedRows, encodedAttributes);
175 }
176
177 public override List<TRowResult> GetRowsWithColumns(string table, List<string> rows, List<string> columns, Dictionary<string, string> attributes)
178 {
179 byte[] tableName = Encode(table);
180 List<byte[]> encodedRows = EncodeStringList(rows);
181 List<byte[]> encodedColumns = EncodeStringList(columns);
182 Dictionary<byte[], byte[]> encodedAttributes = EncodeAttributes(attributes);
183 return client.getRowsWithColumns(tableName, encodedRows, encodedColumns, encodedAttributes);
184 }
185
186 public override void ScannerClose(int id)
187 {
188 client.scannerClose(id);
189 }
190
191 public override void IterateResults(TRowResult result)
192 {
193 foreach (KeyValuePair<byte[], TCell> pair in result.Columns)
194 {
195 Console.WriteLine("\tCol=" + Decode(pair.Key) + ", Value=" + Decode(pair.Value.Value));
196 }
197 }
198
199 private String Decode(byte[] bs)
200 {
201 return UTF8Encoding.Default.GetString(bs);
202 }
203
204 private byte[] Encode(String str)
205 {
206 return UTF8Encoding.Default.GetBytes(str);
207 }
208
209 private Dictionary<byte[], byte[]> EncodeAttributes(Dictionary<String, String> attributes)
210 {
211 Dictionary<byte[], byte[]> encodedAttributes = new Dictionary<byte[],byte[]>();
212 foreach (KeyValuePair<String, String> pair in attributes)
213 {
214 encodedAttributes.Add(Encode(pair.Key), Encode(pair.Value));
215 }
216 return encodedAttributes;
217 }
218
219 private List<byte[]> EncodeStringList(List<String> strings)
220 {
221 List<byte[]> list = new List<byte[]>();
222 if (strings != null)
223 {
224 foreach (String str in strings)
225 {
226 list.Add(Encode(str));
227 }
228 }
229 return list;
230 }
231 }
232 }

上面代码,给出了基本的实现,接着我们给出测试用例,调用我们实现的客户端操作,与HBase表进行交互。实现的测试用例类如下所示:

001 using System;
002 using System.Collections.Generic;
003 using System.Linq;
004 using System.Text;
005 using System.Threading.Tasks;
006
007 namespace HbaseThrift.HBase.Thrift
008 {
009 class Test
010 {
011 private readonly AbstractHBaseThriftService client;
012
013 public Test(String host, int port)
014 {
015 client = new HBaseThriftClient(host, port);
016 }
017
018 public Test() : this("master", 9090)
019 {
020
021 }
022
023 static String RandomlyBirthday()
024 {
025 Random r = new Random();
026 int year = 1900 + r.Next(100);
027 int month = 1 + r.Next(12);
028 int date = 1 + r.Next(30);
029 return year + "-" + month.ToString().PadLeft(2, '0') + "-" + date.ToString().PadLeft(2, '0');
030 }
031
032 static String RandomlyGender()
033 {
034 Random r = new Random();
035 int flag = r.Next(2);
036 return flag == 0 ? "M" : "F";
037 }
038
039 static String RandomlyUserType()
040 {
041 Random r = new Random();
042 int flag = 1 + r.Next(10);
043 return flag.ToString();
044 }
045
046 public void Close()
047 {
048 client.Close();
049 }
050
051 public void CaseForUpdate() {
052 bool writeToWal = false;
053 Dictionary<String, String> attributes = new Dictionary<String, String>(0);
054 string table = SetTable();
055 // put kv pairs
056 for (int i = 0; i < 10000000; i++) {
057 string rowKey = i.ToString().PadLeft(4, '0');
058 Dictionary<String, String> fieldNameValues = new Dictionary<String, String>();
059 fieldNameValues.Add("info:birthday", RandomlyBirthday());
060 fieldNameValues.Add("info:user_type", RandomlyUserType());
061 fieldNameValues.Add("info:gender", RandomlyGender());
062 client.Update(table, rowKey, writeToWal, fieldNameValues, attributes);
063 }
064 }
065
066 public void CaseForDeleteCells() {
067 bool writeToWal = false;
068 Dictionary<String, String> attributes = new Dictionary<String, String>(0);
069 String table = SetTable();
070 // put kv pairs
071 for (long i = 5; i < 10; i++) {
072 String rowKey = i.ToString().PadLeft(4, '0');
073 List<String> columns = new List<String>(0);
074 columns.Add("info:birthday");
075 client.DeleteCells(table, rowKey, writeToWal, columns, attributes);
076 }
077 }
078
079 public void CaseForDeleteRow() {
080 Dictionary<String, String> attributes = new Dictionary<String, String>(0);
081 String table = SetTable();
082 // delete rows
083 for (long i = 5; i < 10; i++) {
084 String rowKey = i.ToString().PadLeft(4, '0');
085 client.DeleteRow(table, rowKey, attributes);
086 }
087 }
088
089 public void CaseForScan() {
090 Dictionary<String, String> attributes = new Dictionary<String, String>(0);
091 String table = SetTable();
092 String startRow = "0005";
093 String stopRow = "0015";
094 List<String> columns = new List<String>(0);
095 columns.Add("info:birthday");
096 int id = client.ScannerOpen(table, startRow, stopRow, columns, attributes);
097 int nbRows = 2;
098 List<TRowResult> results = client.ScannerGetList(id, nbRows);
099 while(results != null) {
100 foreach(TRowResult result in results) {
101 client.IterateResults(result);
102 }
103 results = client.ScannerGetList(id, nbRows);
104 }
105 client.ScannerClose(id);
106 }
107
108 public void CaseForGet() {
109 Dictionary<String, String> attributes = new Dictionary<String, String>(0);
110 String table = SetTable();
111 List<String> rows = new List<String>(0);
112 rows.Add("0009");
113 rows.Add("0098");
114 rows.Add("0999");
115 List<String> columns = new List<String>(0);
116 columns.Add("info:birthday");
117 columns.Add("info:gender");
118 List<TRowResult> results = client.GetRowsWithColumns(table, rows, columns, attributes);
119 foreach(TRowResult result in results) {
120 client.IterateResults(result);
121 }
122 }
123
124 private string SetTable()
125 {
126 string table = "test_info";
127 return table;
128 }
129
130 static void Main(string[] args)
131 {
132 Test test = new Test();
133 //test.CaseForUpdate(); // insert or update rows/cells
134 //test.CaseForDeleteCells(); // delete cells
135 //test.CaseForDeleteRow(); // delete rows
136 test.CaseForScan(); // scan rows
137 //test.CaseForGet(); // get rows
138
139 test.Close();
140 }
141
142 }
143 }

上面的测试可以实现操作Hbase表数据。另外,在生成的Thrift客户端代码中,Iface中给出了全部的服务接口,可以根据需要来选择,客户端Client实现了与Thrift交互的一些逻辑的处理,通过该类对象可以代理HBase提供的Thrift服务。

目录
相关文章
|
9月前
|
存储 算法 安全
如何控制上网行为——基于 C# 实现布隆过滤器算法的上网行为管控策略研究与实践解析
在数字化办公生态系统中,企业对员工网络行为的精细化管理已成为保障网络安全、提升组织效能的核心命题。如何在有效防范恶意网站访问、数据泄露风险的同时,避免过度管控对正常业务运作的负面影响,构成了企业网络安全领域的重要研究方向。在此背景下,数据结构与算法作为底层技术支撑,其重要性愈发凸显。本文将以布隆过滤器算法为研究对象,基于 C# 编程语言开展理论分析与工程实践,系统探讨该算法在企业上网行为管理中的应用范式。
261 8
|
5月前
|
XML 前端开发 C#
C#编程实践:解析HTML文档并执行元素匹配
通过上述步骤,可以在C#中有效地解析HTML文档并执行元素匹配。HtmlAgilityPack提供了一个强大而灵活的工具集,可以处理各种HTML解析任务。
283 19
|
6月前
|
存储 机器学习/深度学习 监控
公司监控软件有哪些?监测方案:基于布隆过滤器的 C# 异常行为检测实践探索
本文探讨了布隆过滤器在公司监控软件中的技术应用,介绍其原理、优势及C#实现代码,助力企业高效构建数据安全防护体系。
174 0
|
8月前
|
监控 算法 安全
公司电脑监控软件关键技术探析:C# 环形缓冲区算法的理论与实践
环形缓冲区(Ring Buffer)是企业信息安全管理中电脑监控系统设计的核心数据结构,适用于高并发、高速率与短时有效的多源异构数据处理场景。其通过固定大小的连续内存空间实现闭环存储,具备内存优化、操作高效、数据时效管理和并发支持等优势。文章以C#语言为例,展示了线程安全的环形缓冲区实现,并结合URL访问记录监控应用场景,分析了其在流量削峰、关键数据保护和高性能处理中的适配性。该结构在日志捕获和事件缓冲中表现出色,对提升监控系统效能具有重要价值。
220 1
|
9月前
|
存储 监控 算法
基于 C# 时间轮算法的控制局域网上网时间与实践应用
在数字化办公与教育环境中,局域网作为内部网络通信的核心基础设施,其精细化管理水平直接影响网络资源的合理配置与使用效能。对局域网用户上网时间的有效管控,已成为企业、教育机构等组织的重要管理需求。这一需求不仅旨在提升员工工作效率、规范学生网络使用行为,更是优化网络带宽资源分配的关键举措。时间轮算法作为一种经典的定时任务管理机制,在局域网用户上网时间管控场景中展现出显著的技术优势。本文将系统阐述时间轮算法的核心原理,并基于 C# 编程语言提供具体实现方案,以期深入剖析该算法在局域网管理中的应用逻辑与实践价值。
230 5
|
分布式计算 Hadoop Shell
熟悉常用的HBase操作
熟悉常用的HBase操作
440 3
熟悉常用的HBase操作
|
开发框架 安全 .NET
全面掌握C#中的类型转换:详解与实践
【8月更文挑战第20天】
546 0
|
测试技术 C# 数据库
C# 一分钟浅谈:测试驱动开发 (TDD) 实践
【10月更文挑战第18天】测试驱动开发(TDD)是一种软件开发方法论,强调先编写测试代码再编写功能代码,以确保代码质量和可维护性。本文从 TDD 的基本概念入手,详细介绍了其核心步骤——编写测试、运行测试并失败、编写代码使测试通过,以及“红绿重构”循环。文章还探讨了 TDD 的优势,包括提高代码质量、促进设计思考、减少调试时间和文档化。此外,文中分析了常见问题及解决方案,如测试覆盖率不足、测试代码过于复杂、忽视重构和测试依赖过多,并通过一个简单的计算器类的代码案例,展示了 TDD 的实际应用过程。
304 1
|
数据采集 C# 数据库
数据验证与错误处理:C#中的实践
【10月更文挑战第1天】在软件开发中,数据验证与错误处理至关重要,不仅能提升程序的健壮性和安全性,还能改善用户体验。本文从基础概念入手,详细介绍了C#中的数据验证方法,包括使用自定义属性和静态方法验证数据,以及常见的错误处理技巧,如Try-Catch-Finally结构和自定义异常。通过具体示例,帮助读者掌握最佳实践,构建高质量应用。
389 3
|
存储 JSON API
HTTP 请求与响应处理:C#中的实践
【10月更文挑战第4天】在现代Web开发中,HTTP协议至关重要,无论构建Web应用还是API开发,都需要熟练掌握HTTP请求与响应处理。本文从C#角度出发,介绍HTTP基础知识,包括请求与响应结构,并通过`HttpClient`库演示如何发送GET请求及处理响应,同时分析常见错误并提供解决方案,助你更高效地完成HTTP相关任务。
519 2