The Extract Function refactoring is the starting point for much of my code clean up. Once a “Main” function gets sufficiently complicated, I pull pieces of it out into their own functions, often with an eye to making them methods of the involved classes.
While working with some rust code, I encountered an opportunity to execute this refactoring on some logging code. Here’s how I executed it.
Here is what the code looks like at the start:
fn main() -> std::io::Result<()> { { let local_ip4 = IpAddr::from_str("0.0.0.0").unwrap(); let listen4_port: u16 = 67; let socket = UdpSocket::bind(&SocketAddr::new(local_ip4, listen4_port))?; socket.set_broadcast(true).expect("set_broadcast call failed"); // Receives a single datagram message on the socket. If `buf` is too small to hold // the message, it will be cut off. let mut buf = [0; 300]; let (_amt, _src) = socket.recv_from(&mut buf)?; let boot_packet = build_boot_packet(&buf); println!("packet received"); println!("opcode = {0}", boot_packet.opcode); println!("hwtype = {0}", boot_packet.hwtype); println!("hw addr len = {0}", boot_packet.hw_addr_len); println!("hop count = {0}", boot_packet.hop_count); println!("txn_id = {:x}", boot_packet.txn_id); println!("num_secs = {:}", boot_packet.num_secs); println!("ips {0} {1} {2} {3}", boot_packet.client_ip, boot_packet.your_ip, boot_packet.server_ip, boot_packet.gateway_ip); println!("Mac Addr: = {:}", boot_packet.client_mac); Ok(()) } |
My next task is to modify the data in the packet in order to send it back to the client. I know that I am going to want to log the data prior to writing it to the socket. That means I am going to end up needing those same log lines, possibly with additional entries.
I also know that I am going to want to see the difference when logging the packets. So…I decide first to implement the copy trait on my structure, and make a duplicate of the packet so I can see the before and after.
let boot_request = build_boot_packet(&buf); let mut boot_packet = boot_request; |
As always: run the code after making this change. I don’t have automated unit tests for this yet, as all I am doing is readying data off the wire…just saying that points at the direction for where my test should go, and I will do that shortly.
For now, I spin up a virtual machine that makes a DHCP request, and I make sure that I can still see the log data.
Now, to extract the logging code, I first wrap that section in a local function. Make sure to call the function after it is defined:
fn log_packet(boot_packet: &BootPacket){ println!("packet received"); println!("opcode = {0}", boot_packet.opcode); println!("hwtype = {0}", boot_packet.hwtype); println!("hw addr len = {0}", boot_packet.hw_addr_len); println!("hop count = {0}", boot_packet.hop_count); println!("txn_id = {:x}", boot_packet.txn_id); println!("num_secs = {:}", boot_packet.num_secs); println!("ips {0} {1} {2} {3}", boot_packet.client_ip, boot_packet.your_ip, boot_packet.server_ip, boot_packet.gateway_ip); println!("Mac Addr: = {:}", boot_packet.client_mac); } log_packet(&boot_packet); |
Again, run the test. If it runs successfully, continue on to moving the log_packet function up to the file level namespace. Run the tests.
The above steps are the main technique, and can be used to extract a function. I am going to take it one step further and convert it to a method implemented on struct BootPacket. The final code looks like this:
impl BootPacket { fn log_packet(&self){ println!("packet received"); println!("opcode = {0}", self.opcode); println!("hwtype = {0}", self.hwtype); println!("hw addr len = {0}", self.hw_addr_len); println!("hop count = {0}", self.hop_count); println!("txn_id = {:x}", self.txn_id); println!("num_secs = {:}", self.num_secs); println!("ips {0} {1} {2} {3}", self.client_ip, self.your_ip, self.server_ip, self.gateway_ip); println!("Mac Addr: = {:}", self.client_mac); } } |