W tym tutorialu postaram się pokazać, jak w C/C++ uzyskać podstawowe informacje o podłączonych do systemu woluminach logicznych. Ale od początku - co to są te woluminy logiczne? Inaczej zwane partycjami lub dyskami logicznymi są wydzielonymi obszarami fizycznego nośnika danych (takiego jak dysk twardy, pendrive, płyta CD czy DVD) i służą do przechowywania informacji. W systemie Windows (bo tego systemu ten tekst dotyczy) każdy dysk logiczny posiada przypisaną do niego literę alfabetu. Pierwsze dwie (A i B) są zarezerwowane dla dyskietek, litera C jest pierwszą dostępną dla dysków twardych i reprezentuje zwykle partycję systemową (na której jest umieszczony system). Należy uważać, żeby nie pomylić fizycznego nośnika z partycją, bo, mimo iż popularnie mówi się np. "dysk c" mowa tu o wydzielonej partycji na którymś z nośników (jeden dysk może być podzielony na wiele woluminów/partycji, każdą reprezentowaną inną literą). Także tutaj będzie pokazane, w jaki sposób można uzyskać dane o partycjach (między innymi o numerze seryjnym woluminu, nie wolno mylić z serialem HDD!).
Ktoś może się zapytać - ale po co nam takie informacje? No cóż, mogą się przydać w wypadku, gdy chcemy zabezpieczyć swój program (aby utrudnić jego rozpowszechnianie bez licencji), lub (jak to jest wykonane np. w GG v8) użyć np. numeru seryjnego jednego z woluminów do szyfrowania danych (w tym wypadku to było szyfrowanie plików komunikatora zawierających dane użytkownika). Następne narzucające się pytanie to: co to jest to WinAPI? Jest to zbiór funkcji, stałych, zmiennych czy struktur umożliwiający działanie programu w systemie Windows. Udostępnia ono wiele nowych możliwości, często niemożliwych do osiągnięcia korzystając z elementów udostępnianych przez język.
Tyle teorii powinno wystarczyć - czas na praktykę! Co nam będzie potrzebne:
- Edytor (w którym będziemy pisali kod), np. Code::Blocks, Microsoft Visual Studio, Notepad++ etc.
- Kompilator, np. MinGW
- System Windows, dzięki któremu będziemy mogli zobaczyć efekty naszej pracy
- Podstawowa wiedza o języku C++
- Trochę cierpliwości (bo mam zwyczaj strasznie lać wodę :P)
Na początku musimy określić, co nam będzie potrzebne do wykonania zadania, czyli jakich funkcji i bibliotek będziemy musieli użyć. Do uzyskania podstawowych informacji o partycji służą 2 funkcje WinAPI - GetDriveType() oraz GetVolumeInformation(). Pierwsza służy do określenia typu nośnika danych, druga udostępnia już pożądane przez nas informacje (np. nazwa woluminu, system plików czy serial). Obie funkcje są udostępnione w pliku nagłówkowym "windows.h", dodatkowo będziemy potrzebowali funkcji umożliwiających wypisanie wyniku na konsolę, czyli standardowe cout z biblioteki "iostream" oraz biblioteki string, która powinna nam ułatwić później zadanie:
#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0])) // Makro, dzięki któremu możemy obliczyć wielkość tablicy z elementami stałej wielkości
#include <windows.h>
#include <iostream>
#include <sstream> // Przyda się później
#include <string>
/**
* Główna funkcja programu
*/
int main(void)
{
return 0;
} // End main()
No dobrze, mamy już szkielet programu, możemy przejść dalej - trzeba ustalić jakie zmienne będą nam potrzebne (w nich będziemy przechowywać informacje o woluminach). Jak możemy zobaczyć na stronach msdn funkcja GetDriveType() zwraca żądaną wartość, więć nie musimy jej nigdzie zapisywać (choć możemy, nikt nam przecież nie broni :)), a jako parametr przyjmuje główny katalog partycji. Nnatomiast GetVolumeInformation() zwraca kod błędu, a argumenty przyjmuje jako pointery (wskaźniki); są to kolejno (w nawiasie typ): głowny katalog partycji (char*), bufor na nazwę partycji (char*), jej wielkość (DWORD), numer seryjny (DWORD*), maksymalna długość elementu ścieżki (DWORD*), flagi systemu plików (DWORD*), bufor na nazwę systemu plików (char*), jego wielkość (DWORD).
Dlaczego DWORD? Dlaczego nie można użyć zwykłego inta? Ma to związek z architekturą komputera PC i Asemblerem, mianowicie int (w językach wysokiego poziomu) ma MINIMUM 16 bitów, czyli 2 bajty, natomiast DWORD (skrót o double WORD, czyli podwójne słowo) ma zawsze 32 bity, czyli 4 bajty. Coś takiego jak DWORD istnieje nawet w Asemblerze, dzięki czemu możliwe jest użycie WinAPI w języku dowolnego poziomu (w c/c++ dword jest zadeklarowany następująco: typedef unsigned long DWORD;).
Wracając do tematu: tworząc wszystkie wymienione powyżej zmienne można od razu stworzyć tablicę zawierającą listę możliwych rodzajów nośników na podstawie wartości zwracanej przez GetDriveType(), czyli (w nawiasie wartość z GetDriveType()): nieznany typ nośnika (0), katalog główny jest błędny (1), napęd wymienny (2), napęd stały (3), zdalny napęd (4), CDROM (5), dysk RAM (6).
Tak więc dopisujemy ten kod do ciała funkcji main programu:
// Zmienne winapi do wyciągania dokładniejszych info o dysku
char fileSystemName[MAX_PATH*5 + 1] = {0}; // Nazwa systemu plików
char volumeName[MAX_PATH*5 + 1] = {0}; // Nazwa partycji
DWORD maxComponentLen = 0; // Maksymalna długość elementu ścieżki
DWORD fileSystemFlags = 0; // Flagi systemu plików (tu pominięte)
DWORD serialNumber = 0; // Numer seryjny woluminu
// Typy dysków
std::string drive_types[] = {"nieznany typ",
"katalog glowny jest bledny",
"naped wymienny (np. pendrive)",
"naped staly (np. dysk twardy lub dysk flash)",
"zdalny naped",
"CDROM",
"dysk RAM"
};
Pozostaje jeszcze to, o co nam chodziło, czyli uzyskanie informacji o wszystkich woluminach zamontowanych w systemie (jak już wspominałem, każda partycja jest reprezentowana przez literę alfabetu). Tak więc musimy "przejść" przez wszystkie możliwe partycje, sprawdzić, czy są one prawidłowe (czyli nie jest to nieznany typ lub z błędnym katalogiem głównym) i jeśli tak, to wyświetlić o nich informacje:
/// Rendering outputu
for(char i = 'C'; i <= 'Z'; ++i) // Sprawdzanie woluminów
{
if(GetDriveType((to_string(i)+":/").c_str()) != 0 && GetDriveType((to_string(i)+":/").c_str()) != 1) // Jeśli wolumin istnieje
{
/// Wyciąganie info o woluminie
if ( GetVolumeInformation((to_string(i)+":/").c_str(), volumeName, ARRAYSIZE(volumeName), &serialNumber, &maxComponentLen, &fileSystemFlags, fileSystemName, ARRAYSIZE(fileSystemName)) )
{
/// Dane dotyczące woluminów logicznych
// Etykieta woluminu
std::cout << "Etykieta woluminu.....................: ";
std::cout << volumeName << "\n";
// Numer seryjny
std::cout << "Numer seryjny woluminu................: ";
std::cout << serialNumber;
// System plików
std::cout << "System plikow.........................: ";
std::cout << fileSystemName << "\n";
// Maksymana długość komponentu
std::cout << "Maksymalna dlugosc komponentu sciezki.: ";
std::cout << maxComponentLen << "\n";
// Typ nośnika
std::cout << "Typ nosnika...........................: ";
std::cout << drive_types[GetDriveType((to_string(i)+":/").c_str())] << "\n";
std::cout << "\n";
} // End if
} // End if
} // End for
Funkcja to_string użyta tu kilkukrotnie zamienia typ char na std::string i ma następującą postać (tutaj przydaje się właśnie biblioteka sstream):
/**
* Konwersja znaku (char) na std::string
*
* @param const char text znak do konwersji
* @return std::string
*/
std::string to_string(const char text)
{
std::stringstream ss;
std::string str;
ss << text;
ss >> str;
return str;
} // End to_string()
No dobrze, ktoś powie, ale co to jest niby za numer seryjny, jak jest on 10 cyfrową liczbą, a polecenie dir (które twierdzi, że też pokazuje serial woluminu) zwraca dziewięcioznakowy ciąg znaków z myślnikiem na piątej pozycji? Rzeczywiście, oba wyniki się od siebie całkowicie różnią, ale tylko na pozór. Pamiętacie, jak mówiłem, że typ DWORD ma 32 bity? Te 8 znaków z wyniku polecenia dir nieprzypadkowo ZAWSZE ma 8 znaków z zakresu od 0 do 9 lub od A do F(nie licząc separatora w postaci myślnika). Już wyjaśniam: w systemie szesnastkowym każda cyfra jest reprezentowana kolejno jako jeden z tych znaków: [0123456789ABCDEF] czyli wartośći od 0 do 15 (szesnaście cyfr). Każdą cyfrę tego systemu można zakodować na 4 bitach (0 - 0000. F - 1111), dzięki czemu 32 bity (wielkość typu DWORD) podzielone przez 4 bity (wielkość potrzebna na jedną cyfrę szesnastkową) daje właśnie te 8 znaków (myślnik pełni rolę ozdobną i pomaga w ewentualnym przepisywaniu). Aby zamienić liczbę z wartości dziesiętnej (którą dostaliśmy z funkcji GetVolumeInformation()) na wartość jak ta z polecenia dir wykorzystamy następującą funkcję:
/**
* Zmiana DWORD'a (32 bity) na liczbę szesnastkową po 16 bitach rozdzieloną myślnikiem zawartą w std::string
*
* @param DWORD data 32-bitowa liczba do konwersji
* @return std::string
*/
std::string to_string_upper_hex(DWORD data)
{
/// Konwersja na stringa (z równoczesną zmianą podstawy systemu liczenia)
std::stringstream ss;
std::string str;
ss << std::hex << data;
ss >> str;
/// Zmiana wielkości liter na wielkie
for(unsigned short i = 0; i < str.length(); ++i)
str[i] = toupper(str[i]);
/// Podział na 2 połowy i rozdzielenie ich średnikiem
str = str.substr(0, 4) + "-" + str.substr(4, 4);
return str;
} // End to_string_upper_hex()
Musimy jeszcza zamienić linijkę odpowiedzialną za wypisanie numeru seryjnego, będzie ona miała taką postać:
std::cout << serialNumber << "(dec)/" << to_string_upper_hex(serialNumber) << "(hex)" << "\n";
No dobrze, osiągnęliśmy już zamierzony efekt, więc czy coś jeszcze można zrobić? Skoro już rozpoczęliśmy zabawę z WinAPI, to czemu nie? Na razie program wygląda monotonnie i jednokolorowo, więc czemu by go trochę nie ożywić? Do zmiany koloru tekstu/tła (UWAGA! tło zmienia się tylko pod tekstem, nie dla całej konsoli) służy funkcja SetConsoleTextAttribute() (także udostępniana przez nagłówek "windows.h"). Przyjmuje ona 2 argumenty - uchwyt do bufora ekranu konsoli oraz atrybuty (opisane dokładniej tutaj ). Aby uzyskać wymagany uchwyt skorzystamy z funkcji GetStdHandle() która zwraca pożądany uchwyt, a jako jedyny argument wymaga podania informacji, do czego chcemy uzyskać dostęp (stdin, stdout, stderr). Jako iż chcemy zmienić kolor tekstu wyświetlanego na stdout podajemy do niej argument STD_OUTPUT_HANDLE:
HANDLE console_out = GetStdHandle(STD_OUTPUT_HANDLE); // Uchwyt do konsoli (aby można było zmieniać kolory)
Dzięki temu uchwyt znajduje się w zmiennej console_out i możemy go już używać. Zmieńmy np. kolor etykiet na intensywny czerwony (atrybuty FOREGROUND_RED i FOREGROUND_INTENSITY), natomiast odczytane wartości na kolor zielony (atrybut FOREGROUND_GREEN):
/// Dane dotyczące woluminów logicznych
// Etykieta woluminu
SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
std::cout << "Etykieta woluminu.....................: ";
SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
std::cout << volumeName << "\n";
// Numer seryjny
SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
std::cout << "Numer seryjny woluminu................: ";
SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
std::cout << serialNumber << "(dec)/" << to_string_upper_hex(serialNumber) << "(hex)" << "\n";
// System plików
SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
std::cout << "System plikow.........................: ";
SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
std::cout << fileSystemName << "\n";
// Maksymana długość komponentu
SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
std::cout << "Maksymalna dlugosc komponentu sciezki.: ";
SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
std::cout << maxComponentLen << "\n";
// Typ nośnika
SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
std::cout << "Typ nosnika...........................: ";
SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
std::cout << drive_types[GetDriveType((to_string(i)+":/").c_str())] << "\n";
Czy wszystko jest ok? Nie do końca - jeśli przyjrzymy się bliżej naszemu kodowi, okaże się, że jedyne, co się w tym fragmencie zmienia, to etykieta (np. "Numer seryjny woluminu......:") oraz wartość parametru. Mając to na względzie można stworzyć dodatkową funkcję (nazwijmy ją np. print_data()), która będzie wyświetlała te dane:
/**
* Wyświetlenie kolejnych parametrów woluminu
*
* @param std::string heading nagłówek do wyświetlenia
* @param std::string val wartość do wyświetlenia
* @return void
*/
void print_data(std::string heading, std::string val)
{
HANDLE console_out = GetStdHandle(STD_OUTPUT_HANDLE); // Uchwyt do konsoli (aby można było zmieniać kolory)
SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
std::cout << heading;
SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
std::cout << val << "\n";
} // End print_data()
Jak widać także zmienna HANDLE console_out została tu przeniesiona z funkcji main, gdzie nie będzie już dłużej potrzebna. I tu pojawia się problem: napisaliśmy, że wartość do wyświetlenia ma typ std::string, a przecież zmienne mają typy std::string, char* lub DWORD! Na szczęście w C++ zostały stworzone szablony (ang. templates), dzięki którym możemy stworzyć funkcję operującą na dowolnym typie danych. Jak to będzie wyglądać? Możemy np. zmodyfikować wcześniejszą funkcję to_string(), aby konwertowała nie tylko typ char, lecz dowolny (bo konwersja dowolnego typu prostego na std::string wygląda tak samo):
/**
* Konwersja danych na std::string
*
* @param T data znak do konwersji
* @return std::string
*/
template<typename T>
std::string to_string(T data)
{
std::stringstream ss;
std::string str;
ss << data;
ss >> str;
return str;
} // End to_string()
I jej definicja:
/// Konwersja danych na zmienną typu string
template<typename T>
std::string to_string(T data);
Jak widać, zmiany ograniczyły się jedynie do dodania w 2 miejscach linii "template" oraz zmiany "char text" na "T data" (przy okazji zmieniłem nazwę zmiennej, aby nie wprowadzała w błąd). Dzięki temu kod wyświetlający dane nie zawiera powtórzeń (zgodnie z regułą DRY) i wygląda przejrzyściej (dodatkowe nowe linie dodane dla przejrzystości):
print_data("Etykieta woluminu.....................: ",
to_string<char*>(volumeName));
print_data("Numer seryjny woluminu................: ",
to_string<DWORD>(serialNumber) + "(dec)/" + to_string_upper_hex(serialNumber) + "(hex)");
print_data("System plikow.........................: ",
to_string<char*>(fileSystemName));
print_data("Maksymalna dlugosc komponentu sciezki.: ",
to_string<DWORD>(maxComponentLen));
print_data("Typ nosnika...........................: ",
drive_types[GetDriveType((to_string<char>(i)+":/").c_str())] );
Jak widać, kodu trochę przybyło, ale program zaczął się wyróżniać :). Jeszcze tylko dodajmy łapanie wyjątków (żeby progrm się brzydko nie wysypał):
// ...
int main(void)
{
try
{
} // End try
catch(const std::exception &e)
{
MessageBox(NULL, e.what(), "Podczas dzialania wystapil blad!", MB_OK);
} // End catch
return 0;
} // End main()
// ...
Użyta tu funkcja MessageBox(), będąca także elementem WinAPI odpowiada za wyświetlenie małego okienka z komunikatem. Jej parametry to, kolejno: okno rodzica (NULL oznacza brak rodzica), treść komunikatu, tekst na pasku tytułu i dodatkowe parametry (MB_OK oznacza wyświetlenie przycisku OK). Zbierzmy teraz wszystko razem i powstanie taki kod:
/************************************************************************************
* Name: hddinfo.cpp *
* Purpose: Extracting basic information about partitions installed in the system *
* Author: Tomasz Stasiak *
* Created: 2011-08-10 *
* Copyright: Tomasz Stasiak *
* License: http://creativecommons.org/licenses/by/2.5/pl/ *
***********************************************************************************/
#define ARRAYSIZE(a) (sizeof(a)/sizeof(a[0]))
#include <windows.h>
#include <iostream>
#include <sstream>
#include <string>
/// Konwersja danych na zmienną typu string
template<typename T>
std::string to_string(T data);
/// Konwersja numeru seryjnego z liczby dziesiętnej na format zwracany przez np. dir
std::string to_string_upper_hex(DWORD data);
/// Wyświetlenie pokolorowanego tekstu
void print_data(std::string heading, std::string val);
/**
* Główna funkcja programu
*/
int main(void)
{
try
{
// Zmienne winapi do wyciągania dokładniejszych info o dysku
char fileSystemName[MAX_PATH*5 + 1] = {0}, volumeName[MAX_PATH*5 + 1] = {0};
DWORD maxComponentLen = 0, fileSystemFlags = 0, serialNumber = 0;
// Typy dysków
std::string drive_types[] = {"nieznany typ",
"katalog glowny jest bledny",
"naped wymienny (np. pendrive)",
"naped staly (np. dysk twardy lub dysk flash)",
"zdalny naped",
"CDROM",
"RAM"
};
/// Rendering outputu
for(char i = 'C'; i <= 'Z'; ++i) // Sprawdzanie woluminów
{
if(GetDriveType((to_string(i)+":/").c_str()) != 0 && GetDriveType((to_string(i)+":/").c_str()) != 1) // Jeśli wolumin istnieje
{
/// Wyciąganie info o woluminie
if ( GetVolumeInformation((to_string(i)+":/").c_str(), volumeName, ARRAYSIZE(volumeName), &serialNumber, &maxComponentLen, &fileSystemFlags, fileSystemName, ARRAYSIZE(fileSystemName)) )
{
/// Dane dotyczące woluminów logicznych
print_data("Etykieta woluminu.....................: ",
to_string<char*>(volumeName));
print_data("Numer seryjny woluminu................: ",
to_string<DWORD>(serialNumber) + "(dec)/" + to_string_upper_hex(serialNumber) + "(hex)");
print_data("System plikow.........................: ",
to_string<char*>(fileSystemName));
print_data("Maksymalna dlugosc komponentu sciezki.: ",
to_string<DWORD>(maxComponentLen));
print_data("Typ nosnika...........................: ",
drive_types[GetDriveType((to_string<char>(i)+":/").c_str())] );
std::cout << "\n";
} // End if
} // End if
} // End for
} // End try
catch(const std::exception &e)
{
MessageBox(NULL, e.what(), "Podczas dzialania wystapil blad!", MB_OK);
} // End catch
return 0;
} // End main()
/**
* Konwersja danych na std::string
*
* @param T data znak do konwersji
* @return std::string
*/
template<typename T>
std::string to_string(T data)
{
std::stringstream ss;
std::string str;
ss << data;
ss >> str;
return str;
} // End to_string()
/**
* Zmiana DWORD'a (32 bity) na liczbę szesnastkową po 16 bitach rozdzieloną myślnikiem zawartą w std::string
*
* @param DWORD data 32-bitowa liczba do konwersji
* @return std::string
*/
std::string to_string_upper_hex(DWORD data)
{
/// Konwersja na stringa (z równoczesną zmianą podstawą systemu liczenia)
std::stringstream ss;
std::string str;
ss << std::hex << data;
ss >> str;
/// Zmiana wielkości liter na wielkie
for(unsigned short i = 0; i < str.length(); ++i)
str[i] = toupper(str[i]);
/// Podział na 2 połowy i rozdzielenie ich średnikiem
str = str.substr(0, 4) + "-" + str.substr(4, 4);
return str;
} // End to_string_upper_hex()
/**
* Wyświetlenie kolejnych parametrów woluminu
*
* @param std::string heading nagłówek do wyświetlenia
* @param std::string val wartość do wyświetlenia
* @return void
*/
void print_data(std::string heading, std::string val)
{
HANDLE console_out = GetStdHandle(STD_OUTPUT_HANDLE); // Uchwyt do konsoli (aby można było zmieniać kolory)
SetConsoleTextAttribute(console_out, FOREGROUND_RED | FOREGROUND_INTENSITY);
std::cout << heading;
SetConsoleTextAttribute(console_out, FOREGROUND_GREEN);
std::cout << val << "\n";
} // End print_data()
Program powinien działać na wszystkich systemach od Windows XP (włącznie) w górę. Dlaczego nie na starszych? Winowajcą jest funkcja GetDriveType() udostępniona dopiero w tymże systemie.
Jeszcze tylko paczka z kodem + skompilowany program.
|
Komentarze
Chodzi raczej o pewne wolne spostrzeżenia, które być może dadzą Ci do myślenia :)
Jeśli chodzi o stałe używane tylko raz, to powiem Ci, iż zawsze się mówi, że tylko raz, a potem się okazuje że jednak 2, czy 3... I o takie błędy najłatwiej. Szczególnie, jak zrobisz coś takiego:
float n = 2;
.. kod kod kod...
for( ... i < 2)
po pewnym czasie stwierdzisz, że n = 3... I zaczynają się schody :). Jeśli bardzo zależy Ci na optymalizowaiu wydajności, to można użyć #define, i na etapie prekompilacji wszystko się "powkleja" :). Choć z #define to też są różne szkoły. W sensie - używać, czy nie :)
Zdecydowanie polecam:
http://www.yarpo.pl/2010/12/01/ksiazka-czysty-kod-podrecznik-dobrego-programisty/
sądzę, że jesteś na etapie, na którym nie tyle należy uczyć się pisać kompilowalny i działający kod. Czas uczyć się pisać _naprawdę_ dobry kod :) [nie, aby istniał jeden słuszny _naprawdę_ dobry sposób na to ;)]
Jeśli chodzi o "martwy kod", jak to nazywasz, to czy ja wiem, czy jest taki zły? Widoczne tu opisy funkcji, jej parametrów, etc. mimo, iż nie wspierane przez większość IDE, czasami się przydają (np. przy generowaniu dokumentacji).
Co do tego kodu:
const int DEFAULT_SN_BEGI N= 0;
DWORD voluminSerialNu mber = DEFAULT_SN_BEGI N;
to już uważam za przesadę. Czy naprawdę potrzeba tworzyć dodatkową zmienną, aby czemuś nadać wartość 0?
@2 - tak, wiem, że funkcje powinny być krótkie, ale przyzwyczajenia robią swoje - jakoś wygodniej mimo wszystko zarządza mi się kodem "w kupie" :)
BTW jak znajdę trochę czasu to chyba to jednak ogarnę (bo wiem, że dałoby się go dzięki temu skrócić o jakieś 20-30%) :)
http://e-exam.googlecode.com/svn/trunk/dystrybucja/aplikacja-www/src/java/controllers/MergeAnswers.java
Najczesciej możesz rozbić każde zadanie na kilka mniejszych. Niech funkcja robi jedną rzecz - zapisuje dane do pliku:
function saveData(data)
{
if (is_valid(data) )
{
file = open_file();
lock(file);
// tu jeszcze sprawdzenie rozmiaru danych, bo moze trzeba podzielic na strony, bloki, cokolwiek
// zapis
// zamkniecie pliku
// zapisanie w logu
}
else
{
error('komunikat');
}
}
i krótka funkcja stała się długa. Wiele dziwnych ifów i zagnieżdżeń. Może tak:
function saveData(data)
{
if (is_valid(data) )
{
write_to_file(d ata);
}
else
{
error('komunikat');
}
}
i wszystko związane z zapisem jest we write_to_file. Co więcej łatwiej jest utrzymać pewność, że jakaś funkcja działa dobrze. Potrzebujesz się upewnić, że któryś fragment działa dobrze - robisz test TYLKO tego kawałka, np Unit test [lub kilka].
To jest jak matematyka. Masz najpierw aksjomaty i operatory. Na ich podstawie tworzysz twierdzenia. Potem mając już te twierdzenia robisz kolejne, zakładając, że dotychczas znane i udowodnione są prawidłowe. Małe funkcje, ale których działania jesteś pewien :)
DWORD serialNumber = 0; // Numer seryjny woluminu
czy nie lepiej:
DWORD serialNumber = 0;
albo:
DWORD voluminSerialNu mber = 0;
albo:
const int DEFAULT_SN_BEGI N= 0;
DWORD voluminSerialNu mber = DEFAULT_SN_BEGI N;
Pamiętaj, że każdy komentarz jest kodem, który trzeba pielęgnować. Zatem im mniej "martwe go" kodu (komentarze nie są wspierane przez IDE, podczas refaktoryzacji ich logika nie jest automatycznie zmieniana) tym lepiej.
Niesłusznie ktoś kiedyś głupio powiedział, że dobry kod ma komentarze! Dobry kod ich nie potrzebuje. Nazwy zmiennych i kolejność kodu pozwalają na to, aby ograniczyć komentarze do absolutnego minimum. To nie asembler, gdzie z kodu trudno było zrozumieć, co się dzieje :)
PS. Oczywiście nie uważam, aby Twój kod był zły. Po prostu widzę, że masz smykałkę i sądzę, że słusznie odczytasz ten kometarz :)
Polecam: http://youthcoders.net/recenzje/ksiazki/575-recenzja-czysty-kod.html